@openclaw/lobster 2026.9.5 → 2026.9.7
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/assets/icon.png +0 -0
- package/dist/index.js +8 -294
- package/node_modules/@clawdbot/lobster/README.md +20 -133
- package/node_modules/@clawdbot/lobster/VISION.md +15 -238
- package/node_modules/@clawdbot/lobster/bin/clawd.invoke.js +2 -21
- package/node_modules/@clawdbot/lobster/bin/invoke.js +19 -0
- package/node_modules/@clawdbot/lobster/bin/lobster.js +2 -21
- package/node_modules/@clawdbot/lobster/bin/openclaw.invoke.js +2 -24
- package/node_modules/@clawdbot/lobster/dist/src/cli.js +32 -48
- package/node_modules/@clawdbot/lobster/dist/src/cli.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/command_tokens.js +81 -0
- package/node_modules/@clawdbot/lobster/dist/src/command_tokens.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js +1 -12
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/dedupe.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js +2 -14
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/group_by.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js +1 -337
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/llm_invoke.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js +2 -13
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/map.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js +4 -21
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/sort.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/state.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js +2 -13
- package/node_modules/@clawdbot/lobster/dist/src/commands/stdlib/template.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js +0 -18
- package/node_modules/@clawdbot/lobster/dist/src/commands/workflows/workflows_run.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/core/llm_accounting.js +304 -0
- package/node_modules/@clawdbot/lobster/dist/src/core/llm_accounting.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/core/value_path.js +11 -0
- package/node_modules/@clawdbot/lobster/dist/src/core/value_path.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/parser.js +2 -67
- package/node_modules/@clawdbot/lobster/dist/src/parser.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/index.js +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js +5 -64
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/pr-monitor.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/read_pr.js +45 -0
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/read_pr.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/snapshot.js +45 -0
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/snapshot.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js +9 -47
- package/node_modules/@clawdbot/lobster/dist/src/recipes/github/stages/pr-view.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js +18 -135
- package/node_modules/@clawdbot/lobster/dist/src/sdk/Lobster.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/sdk/index.js +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js +1 -2
- package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/approve.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js +2 -8
- package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/diff.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js +15 -81
- package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/exec.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js +3 -9
- package/node_modules/@clawdbot/lobster/dist/src/sdk/primitives/state.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js +0 -12
- package/node_modules/@clawdbot/lobster/dist/src/sdk/runtime.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/sdk/state_env.js +6 -0
- package/node_modules/@clawdbot/lobster/dist/src/sdk/state_env.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/state/atomic_file.js +192 -0
- package/node_modules/@clawdbot/lobster/dist/src/state/atomic_file.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/state/store.js +2 -188
- package/node_modules/@clawdbot/lobster/dist/src/state/store.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/workflows/dry_run.js +262 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/dry_run.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/expressions.js +345 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/expressions.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js +21 -1036
- package/node_modules/@clawdbot/lobster/dist/src/workflows/file.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js +8 -77
- package/node_modules/@clawdbot/lobster/dist/src/workflows/github_pr_monitor.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js +1 -7
- package/node_modules/@clawdbot/lobster/dist/src/workflows/graph.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/src/workflows/load.js +347 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/load.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/types.js +2 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/types.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/values.js +93 -0
- package/node_modules/@clawdbot/lobster/dist/src/workflows/values.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js +1 -0
- package/node_modules/@clawdbot/lobster/dist/test/cancellation.test.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/test/cli_run_errors.test.js +53 -0
- package/node_modules/@clawdbot/lobster/dist/test/cli_run_errors.test.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/test/command_arguments.test.js +82 -0
- package/node_modules/@clawdbot/lobster/dist/test/command_arguments.test.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js +49 -19
- package/node_modules/@clawdbot/lobster/dist/test/cost_tracker.test.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/test/helpers/node_shell_fixture.js +17 -0
- package/node_modules/@clawdbot/lobster/dist/test/helpers/node_shell_fixture.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js +2 -0
- package/node_modules/@clawdbot/lobster/dist/test/llm_invoke.test.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/test/pipeline_resume_state.test.js +65 -0
- package/node_modules/@clawdbot/lobster/dist/test/pipeline_resume_state.test.js.map +1 -0
- package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js +5 -3
- package/node_modules/@clawdbot/lobster/dist/test/process_output_limit.test.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js +72 -25
- package/node_modules/@clawdbot/lobster/dist/test/sdk_exec.test.js.map +1 -1
- package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js +1 -1
- package/node_modules/@clawdbot/lobster/dist/test/workflow_graph.test.js.map +1 -1
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/README.md +1 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/SECURITY.md +62 -13
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/index.js +40 -9
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/lib/schemes.js +76 -16
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/lib/utils.js +54 -10
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/package.json +1 -1
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/fixtures/uri-js-parse.json +1 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/ipv6-validation.test.js +130 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/mailto-authority-security.test.js +125 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/mailto-field-name-security.test.js +151 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/security-normalization.test.js +38 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/types/index.d.ts +16 -0
- package/node_modules/@clawdbot/lobster/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/@clawdbot/lobster/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
- package/node_modules/@clawdbot/lobster/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/@clawdbot/lobster/node_modules/yaml/dist/nodes/Alias.js +25 -23
- package/node_modules/@clawdbot/lobster/node_modules/yaml/package.json +1 -1
- package/node_modules/@clawdbot/lobster/package.json +6 -8
- package/node_modules/typebox/build/compile/code.mjs +1 -2
- package/node_modules/typebox/build/compile/validator.mjs +5 -1
- package/node_modules/typebox/build/format/iri.mjs +16 -9
- package/node_modules/typebox/build/guard/emit.mjs +2 -2
- package/node_modules/typebox/build/guard/guard.d.mts +7 -5
- package/node_modules/typebox/build/guard/guard.mjs +14 -9
- package/node_modules/typebox/build/guard/index.mjs +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
- package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
- package/node_modules/typebox/build/schema/build.mjs +2 -3
- package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
- package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
- package/node_modules/typebox/build/schema/parse.mjs +3 -0
- package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
- package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
- package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
- package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
- package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
- package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
- package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
- package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
- package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
- package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +3 -1
- package/package.json +6 -6
- package/dist/runtime-api.js +0 -3
- package/node_modules/@clawdbot/lobster/dist/src/sdk/token.js +0 -9
- package/node_modules/@clawdbot/lobster/dist/src/sdk/token.js.map +0 -1
- /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
|
@@ -1,29 +1,25 @@
|
|
|
1
|
+
import { dryRunWorkflow } from "./dry_run.js";
|
|
2
|
+
import { createRecord, mergeEnv, resolveCwd, resolveInputValue, resolveShellStdin, resolveWorkflowStepArgs, } from "./values.js";
|
|
3
|
+
import { loadWorkflowFile, getStepExecution, isApprovalStep, isInputStep } from "./load.js";
|
|
4
|
+
import { evaluateCondition, resolveTemplate } from "./expressions.js";
|
|
1
5
|
import { promises as fsp } from "node:fs";
|
|
2
6
|
import path from "node:path";
|
|
3
|
-
import { parse as parseYaml } from "yaml";
|
|
4
7
|
import { randomUUID } from "node:crypto";
|
|
5
8
|
import { isDeepStrictEqual } from "node:util";
|
|
6
9
|
import { PassThrough } from "node:stream";
|
|
7
10
|
import { parsePipeline } from "../parser.js";
|
|
8
11
|
import { runPipeline } from "../runtime.js";
|
|
9
12
|
import { encodeToken, decodeToken } from "../token.js";
|
|
10
|
-
import { cleanupApprovalIndexByStateKey, consumeResumeState, createApprovalIndex, deleteStateJsonWithBoundedResumeCleanup, deleteResumeStateWithRollback, deleteStateJson, deleteUnconsumedResumeState, isConsumedResumeState, readStateJsonWithLock, restoreConsumedResumeState, writeStateJson, } from "../state/store.js";
|
|
13
|
+
import { cleanupApprovalIndexByStateKey, consumeResumeState, createApprovalIndex, deleteStateJsonWithBoundedResumeCleanup, deleteResumeStateWithRollback, deleteStateJson, deleteUnconsumedResumeState, isConsumedResumeState, readStateJsonWithLock, restoreConsumedResumeState, writeStateJson, stableStringify, } from "../state/store.js";
|
|
11
14
|
import { readLineFromStream } from "../read_line.js";
|
|
12
15
|
import { compileCached } from "../validation.js";
|
|
13
|
-
import { createLlmSpendLedger, llmProvenanceOf, restoreLlmProvenance, } from "../
|
|
16
|
+
import { createLlmSpendLedger, carryLlmProvenance, markLlmProvenanceReplayed, llmProvenanceOf, restoreLlmProvenance, } from "../core/llm_accounting.js";
|
|
14
17
|
import { CostLimitExceededError, CostTracker } from "../core/cost_tracker.js";
|
|
15
18
|
import { withRetry, resolveRetryConfig } from "../core/retry.js";
|
|
16
19
|
import { RequestInputResumeError, validateCommandInputState, } from "../input_request.js";
|
|
17
20
|
import { runAbortableProcess } from "../abortable_process.js";
|
|
21
|
+
export { loadWorkflowFile } from "./load.js";
|
|
18
22
|
const NON_RETRYABLE_STEP_ERROR = Symbol("lobster.nonRetryableStepError");
|
|
19
|
-
function createRecord(source) {
|
|
20
|
-
const record = Object.create(null);
|
|
21
|
-
if (!source)
|
|
22
|
-
return record;
|
|
23
|
-
for (const [key, value] of Object.entries(source))
|
|
24
|
-
record[key] = value;
|
|
25
|
-
return record;
|
|
26
|
-
}
|
|
27
23
|
function markStepErrorNonRetryable(error) {
|
|
28
24
|
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
29
25
|
Object.defineProperty(normalized, NON_RETRYABLE_STEP_ERROR, { value: true });
|
|
@@ -67,320 +63,6 @@ class WorkflowPipelineInputSuspension extends Error {
|
|
|
67
63
|
this.pipelineInput = pipelineInput;
|
|
68
64
|
}
|
|
69
65
|
}
|
|
70
|
-
export async function loadWorkflowFile(filePath) {
|
|
71
|
-
const text = await fsp.readFile(filePath, "utf8");
|
|
72
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
73
|
-
const parsed = ext === ".json" ? JSON.parse(text) : parseYaml(text);
|
|
74
|
-
if (!parsed || typeof parsed !== "object") {
|
|
75
|
-
throw new Error("Workflow file must be a JSON/YAML object");
|
|
76
|
-
}
|
|
77
|
-
const steps = parsed.steps;
|
|
78
|
-
if (!Array.isArray(steps) || steps.length === 0) {
|
|
79
|
-
throw new Error("Workflow file requires a non-empty steps array");
|
|
80
|
-
}
|
|
81
|
-
const costLimit = parsed.cost_limit;
|
|
82
|
-
if (costLimit !== undefined) {
|
|
83
|
-
if (!costLimit || typeof costLimit !== "object" || Array.isArray(costLimit)) {
|
|
84
|
-
throw new Error("Workflow cost_limit must be an object");
|
|
85
|
-
}
|
|
86
|
-
if (!Number.isFinite(Number(costLimit.max_usd)) || Number(costLimit.max_usd) < 0) {
|
|
87
|
-
throw new Error("Workflow cost_limit.max_usd must be a non-negative number");
|
|
88
|
-
}
|
|
89
|
-
if (costLimit.action !== undefined &&
|
|
90
|
-
costLimit.action !== "warn" &&
|
|
91
|
-
costLimit.action !== "stop") {
|
|
92
|
-
throw new Error('Workflow cost_limit.action must be "warn" or "stop"');
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
const seen = new Set();
|
|
96
|
-
for (const step of steps) {
|
|
97
|
-
if (!step || typeof step !== "object") {
|
|
98
|
-
throw new Error("Workflow step must be an object");
|
|
99
|
-
}
|
|
100
|
-
if (!step.id || typeof step.id !== "string") {
|
|
101
|
-
throw new Error("Workflow step requires an id");
|
|
102
|
-
}
|
|
103
|
-
if (step.workflow !== undefined && typeof step.workflow !== "string") {
|
|
104
|
-
throw new Error(`Workflow step ${step.id} workflow must be a string (file path)`);
|
|
105
|
-
}
|
|
106
|
-
if (typeof step.workflow === "string" && !step.workflow.trim()) {
|
|
107
|
-
throw new Error(`Workflow step ${step.id} workflow path cannot be blank`);
|
|
108
|
-
}
|
|
109
|
-
if (step.workflow_args !== undefined) {
|
|
110
|
-
if (!step.workflow_args ||
|
|
111
|
-
typeof step.workflow_args !== "object" ||
|
|
112
|
-
Array.isArray(step.workflow_args)) {
|
|
113
|
-
throw new Error(`Workflow step ${step.id} workflow_args must be a plain object`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if (step.parallel !== undefined &&
|
|
117
|
-
(!step.parallel || typeof step.parallel !== "object" || Array.isArray(step.parallel))) {
|
|
118
|
-
throw new Error(`Workflow step ${step.id} parallel must be an object`);
|
|
119
|
-
}
|
|
120
|
-
const isParallel = Boolean(step.parallel && typeof step.parallel === "object" && !Array.isArray(step.parallel));
|
|
121
|
-
if (isParallel) {
|
|
122
|
-
const parallel = step.parallel;
|
|
123
|
-
if (!Array.isArray(parallel.branches) || parallel.branches.length === 0) {
|
|
124
|
-
throw new Error(`Workflow step ${step.id} parallel requires a non-empty branches array`);
|
|
125
|
-
}
|
|
126
|
-
if (parallel.wait !== undefined && parallel.wait !== "all" && parallel.wait !== "any") {
|
|
127
|
-
throw new Error(`Workflow step ${step.id} parallel wait must be "all" or "any"`);
|
|
128
|
-
}
|
|
129
|
-
if (parallel.timeout_ms !== undefined &&
|
|
130
|
-
(typeof parallel.timeout_ms !== "number" ||
|
|
131
|
-
!Number.isFinite(parallel.timeout_ms) ||
|
|
132
|
-
!Number.isInteger(parallel.timeout_ms) ||
|
|
133
|
-
parallel.timeout_ms < 1 ||
|
|
134
|
-
parallel.timeout_ms > 2_147_483_647)) {
|
|
135
|
-
throw new Error(`Workflow step ${step.id} parallel timeout_ms must be a positive integer between 1 and 2147483647`);
|
|
136
|
-
}
|
|
137
|
-
const branchIds = new Set();
|
|
138
|
-
for (const branch of parallel.branches) {
|
|
139
|
-
if (!branch || typeof branch !== "object") {
|
|
140
|
-
throw new Error(`Workflow step ${step.id} parallel branches must be objects`);
|
|
141
|
-
}
|
|
142
|
-
if (!branch.id || typeof branch.id !== "string") {
|
|
143
|
-
throw new Error(`Workflow step ${step.id} parallel branch requires an id`);
|
|
144
|
-
}
|
|
145
|
-
if (branch.id === step.id) {
|
|
146
|
-
throw new Error(`Workflow step ${step.id} parallel branch id cannot match the step id`);
|
|
147
|
-
}
|
|
148
|
-
if (branchIds.has(branch.id)) {
|
|
149
|
-
throw new Error(`Workflow step ${step.id} duplicate parallel branch id: ${branch.id}`);
|
|
150
|
-
}
|
|
151
|
-
if (seen.has(branch.id)) {
|
|
152
|
-
throw new Error(`Duplicate workflow id across steps/parallel branches: ${branch.id}`);
|
|
153
|
-
}
|
|
154
|
-
branchIds.add(branch.id);
|
|
155
|
-
const branchShell = typeof branch.run === "string" ? branch.run : branch.command;
|
|
156
|
-
const branchPipeline = typeof branch.pipeline === "string" ? branch.pipeline : undefined;
|
|
157
|
-
const branchExecCount = Number(Boolean(branchShell)) + Number(Boolean(branchPipeline));
|
|
158
|
-
if (branchExecCount === 0) {
|
|
159
|
-
throw new Error(`Workflow step ${step.id} parallel branch ${branch.id} requires run, command, or pipeline`);
|
|
160
|
-
}
|
|
161
|
-
if (branchExecCount > 1) {
|
|
162
|
-
throw new Error(`Workflow step ${step.id} parallel branch ${branch.id} can only define one of run, command, or pipeline`);
|
|
163
|
-
}
|
|
164
|
-
if (branch.run !== undefined && typeof branch.run !== "string") {
|
|
165
|
-
throw new Error(`Workflow step ${step.id} parallel branch ${branch.id} run must be a string`);
|
|
166
|
-
}
|
|
167
|
-
if (branch.command !== undefined && typeof branch.command !== "string") {
|
|
168
|
-
throw new Error(`Workflow step ${step.id} parallel branch ${branch.id} command must be a string`);
|
|
169
|
-
}
|
|
170
|
-
if (branch.pipeline !== undefined && typeof branch.pipeline !== "string") {
|
|
171
|
-
throw new Error(`Workflow step ${step.id} parallel branch ${branch.id} pipeline must be a string`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (step.for_each !== undefined && typeof step.for_each !== "string") {
|
|
176
|
-
throw new Error(`Workflow step ${step.id} for_each must be a string (step reference expression)`);
|
|
177
|
-
}
|
|
178
|
-
const isForEach = typeof step.for_each === "string";
|
|
179
|
-
if (isForEach) {
|
|
180
|
-
if (!Array.isArray(step.steps) || step.steps.length === 0) {
|
|
181
|
-
throw new Error(`Workflow step ${step.id} for_each requires a non-empty steps array`);
|
|
182
|
-
}
|
|
183
|
-
if (step.batch_size !== undefined &&
|
|
184
|
-
(typeof step.batch_size !== "number" ||
|
|
185
|
-
!Number.isInteger(step.batch_size) ||
|
|
186
|
-
step.batch_size < 1)) {
|
|
187
|
-
throw new Error(`Workflow step ${step.id} batch_size must be a positive integer`);
|
|
188
|
-
}
|
|
189
|
-
if (step.pause_ms !== undefined &&
|
|
190
|
-
(typeof step.pause_ms !== "number" || !Number.isFinite(step.pause_ms) || step.pause_ms < 0)) {
|
|
191
|
-
throw new Error(`Workflow step ${step.id} pause_ms must be a finite non-negative number`);
|
|
192
|
-
}
|
|
193
|
-
if (isApprovalStep(step.approval)) {
|
|
194
|
-
throw new Error(`Workflow step ${step.id} for_each steps cannot define approval (use a separate step after the loop)`);
|
|
195
|
-
}
|
|
196
|
-
if (isInputStep(step.input)) {
|
|
197
|
-
throw new Error(`Workflow step ${step.id} for_each steps cannot define input (use a separate step after the loop)`);
|
|
198
|
-
}
|
|
199
|
-
if (step.stdin !== undefined && step.stdin !== null) {
|
|
200
|
-
throw new Error(`Workflow step ${step.id} for_each steps cannot define stdin (loop input comes from the for_each expression)`);
|
|
201
|
-
}
|
|
202
|
-
const loopShell = typeof step.run === "string" ? step.run : step.command;
|
|
203
|
-
const loopPipeline = typeof step.pipeline === "string" ? step.pipeline : undefined;
|
|
204
|
-
if (loopShell || loopPipeline || step.workflow || step.parallel) {
|
|
205
|
-
throw new Error(`Workflow step ${step.id} for_each cannot also define run, command, pipeline, workflow, or parallel`);
|
|
206
|
-
}
|
|
207
|
-
if (step.item_var !== undefined && typeof step.item_var !== "string") {
|
|
208
|
-
throw new Error(`Workflow step ${step.id} item_var must be a string`);
|
|
209
|
-
}
|
|
210
|
-
if (step.index_var !== undefined && typeof step.index_var !== "string") {
|
|
211
|
-
throw new Error(`Workflow step ${step.id} index_var must be a string`);
|
|
212
|
-
}
|
|
213
|
-
const loopItemVar = step.item_var ?? "item";
|
|
214
|
-
const loopIndexVar = step.index_var ?? "index";
|
|
215
|
-
if (loopItemVar === loopIndexVar) {
|
|
216
|
-
throw new Error(`Workflow step ${step.id} item_var and index_var cannot be the same`);
|
|
217
|
-
}
|
|
218
|
-
const subStepIds = new Set();
|
|
219
|
-
for (const sub of step.steps) {
|
|
220
|
-
if (!sub || typeof sub !== "object" || !sub.id || typeof sub.id !== "string") {
|
|
221
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step requires an id`);
|
|
222
|
-
}
|
|
223
|
-
if (sub.id === loopItemVar || sub.id === loopIndexVar) {
|
|
224
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step id '${sub.id}' conflicts with loop variable`);
|
|
225
|
-
}
|
|
226
|
-
if (subStepIds.has(sub.id)) {
|
|
227
|
-
throw new Error(`Workflow step ${step.id} duplicate for_each sub-step id: ${sub.id}`);
|
|
228
|
-
}
|
|
229
|
-
subStepIds.add(sub.id);
|
|
230
|
-
if (isApprovalStep(sub.approval) || isInputStep(sub.input)) {
|
|
231
|
-
throw new Error(`Workflow step ${step.id} for_each sub-steps cannot contain approval or input steps`);
|
|
232
|
-
}
|
|
233
|
-
if (sub.run !== undefined && typeof sub.run !== "string") {
|
|
234
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} run must be a string`);
|
|
235
|
-
}
|
|
236
|
-
if (sub.command !== undefined && typeof sub.command !== "string") {
|
|
237
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} command must be a string`);
|
|
238
|
-
}
|
|
239
|
-
if (sub.pipeline !== undefined && typeof sub.pipeline !== "string") {
|
|
240
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} pipeline must be a string`);
|
|
241
|
-
}
|
|
242
|
-
if (sub.workflow || sub.parallel || sub.for_each) {
|
|
243
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} cannot define workflow, parallel, or for_each`);
|
|
244
|
-
}
|
|
245
|
-
const subShell = typeof sub.run === "string" && sub.run.trim()
|
|
246
|
-
? sub.run
|
|
247
|
-
: typeof sub.command === "string" && sub.command.trim()
|
|
248
|
-
? sub.command
|
|
249
|
-
: undefined;
|
|
250
|
-
const subPipeline = typeof sub.pipeline === "string" && sub.pipeline.trim() ? sub.pipeline : undefined;
|
|
251
|
-
if (!subShell && !subPipeline) {
|
|
252
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} requires run, command, or pipeline`);
|
|
253
|
-
}
|
|
254
|
-
if (Number(Boolean(subShell)) + Number(Boolean(subPipeline)) > 1) {
|
|
255
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} can only define one of run, command, or pipeline`);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const shellCommand = typeof step.run === "string" ? step.run : step.command;
|
|
260
|
-
const pipeline = typeof step.pipeline === "string" ? step.pipeline : undefined;
|
|
261
|
-
const workflowRef = typeof step.workflow === "string" && step.workflow.trim() ? step.workflow : undefined;
|
|
262
|
-
const executionCount = Number(Boolean(shellCommand)) +
|
|
263
|
-
Number(Boolean(pipeline)) +
|
|
264
|
-
Number(Boolean(workflowRef)) +
|
|
265
|
-
Number(isParallel) +
|
|
266
|
-
Number(isForEach);
|
|
267
|
-
if (executionCount === 0 && !isApprovalStep(step.approval) && !isInputStep(step.input)) {
|
|
268
|
-
throw new Error(`Workflow step ${step.id} requires run, command, pipeline, workflow, parallel, for_each, approval, or input`);
|
|
269
|
-
}
|
|
270
|
-
if (executionCount > 1) {
|
|
271
|
-
throw new Error(`Workflow step ${step.id} can only define one of run, command, pipeline, workflow, parallel, or for_each`);
|
|
272
|
-
}
|
|
273
|
-
if (executionCount > 0 && isInputStep(step.input)) {
|
|
274
|
-
throw new Error(`Workflow step ${step.id} input steps cannot define run, command, pipeline, workflow, parallel, or for_each`);
|
|
275
|
-
}
|
|
276
|
-
if (isApprovalStep(step.approval) && isInputStep(step.input)) {
|
|
277
|
-
throw new Error(`Workflow step ${step.id} cannot define both approval and input`);
|
|
278
|
-
}
|
|
279
|
-
if (step.run !== undefined && typeof step.run !== "string") {
|
|
280
|
-
throw new Error(`Workflow step ${step.id} run must be a string`);
|
|
281
|
-
}
|
|
282
|
-
if (step.command !== undefined && typeof step.command !== "string") {
|
|
283
|
-
throw new Error(`Workflow step ${step.id} command must be a string`);
|
|
284
|
-
}
|
|
285
|
-
if (step.pipeline !== undefined && typeof step.pipeline !== "string") {
|
|
286
|
-
throw new Error(`Workflow step ${step.id} pipeline must be a string`);
|
|
287
|
-
}
|
|
288
|
-
if (step.input !== undefined && !isInputStep(step.input)) {
|
|
289
|
-
throw new Error(`Workflow step ${step.id} input must be an object`);
|
|
290
|
-
}
|
|
291
|
-
if (step.input && typeof step.input.prompt !== "string") {
|
|
292
|
-
throw new Error(`Workflow step ${step.id} input.prompt must be a string`);
|
|
293
|
-
}
|
|
294
|
-
if (step.input &&
|
|
295
|
-
(step.input.responseSchema === undefined || typeof step.input.responseSchema !== "object")) {
|
|
296
|
-
throw new Error(`Workflow step ${step.id} input.responseSchema must be an object`);
|
|
297
|
-
}
|
|
298
|
-
if (step.input) {
|
|
299
|
-
try {
|
|
300
|
-
compileCached(step.input.responseSchema);
|
|
301
|
-
}
|
|
302
|
-
catch (err) {
|
|
303
|
-
throw new Error(`Workflow step ${step.id} input.responseSchema is invalid: ${err?.message ?? String(err)}`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (step.approval && typeof step.approval === "object" && !Array.isArray(step.approval)) {
|
|
307
|
-
const approval = step.approval;
|
|
308
|
-
if (approval.initiated_by !== undefined && typeof approval.initiated_by !== "string") {
|
|
309
|
-
throw new Error(`Workflow step ${step.id} approval.initiated_by must be a string`);
|
|
310
|
-
}
|
|
311
|
-
if (approval.initiatedBy !== undefined && typeof approval.initiatedBy !== "string") {
|
|
312
|
-
throw new Error(`Workflow step ${step.id} approval.initiatedBy must be a string`);
|
|
313
|
-
}
|
|
314
|
-
if (approval.required_approver !== undefined &&
|
|
315
|
-
typeof approval.required_approver !== "string") {
|
|
316
|
-
throw new Error(`Workflow step ${step.id} approval.required_approver must be a string`);
|
|
317
|
-
}
|
|
318
|
-
if (approval.requiredApprover !== undefined &&
|
|
319
|
-
typeof approval.requiredApprover !== "string") {
|
|
320
|
-
throw new Error(`Workflow step ${step.id} approval.requiredApprover must be a string`);
|
|
321
|
-
}
|
|
322
|
-
if (approval.require_different_approver !== undefined &&
|
|
323
|
-
typeof approval.require_different_approver !== "boolean") {
|
|
324
|
-
throw new Error(`Workflow step ${step.id} approval.require_different_approver must be a boolean`);
|
|
325
|
-
}
|
|
326
|
-
if (approval.requireDifferentApprover !== undefined &&
|
|
327
|
-
typeof approval.requireDifferentApprover !== "boolean") {
|
|
328
|
-
throw new Error(`Workflow step ${step.id} approval.requireDifferentApprover must be a boolean`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (step.timeout_ms !== undefined &&
|
|
332
|
-
(typeof step.timeout_ms !== "number" ||
|
|
333
|
-
!Number.isFinite(step.timeout_ms) ||
|
|
334
|
-
!Number.isInteger(step.timeout_ms) ||
|
|
335
|
-
step.timeout_ms < 1 ||
|
|
336
|
-
step.timeout_ms > 2_147_483_647)) {
|
|
337
|
-
throw new Error(`Workflow step ${step.id} timeout_ms must be a positive integer between 1 and 2147483647`);
|
|
338
|
-
}
|
|
339
|
-
if (step.on_error !== undefined &&
|
|
340
|
-
step.on_error !== "stop" &&
|
|
341
|
-
step.on_error !== "continue" &&
|
|
342
|
-
step.on_error !== "skip_rest") {
|
|
343
|
-
throw new Error(`Workflow step ${step.id} on_error must be "stop", "continue", or "skip_rest"`);
|
|
344
|
-
}
|
|
345
|
-
if (step.retry !== undefined) {
|
|
346
|
-
if (!step.retry || typeof step.retry !== "object" || Array.isArray(step.retry)) {
|
|
347
|
-
throw new Error(`Workflow step ${step.id} retry must be an object`);
|
|
348
|
-
}
|
|
349
|
-
const r = step.retry;
|
|
350
|
-
if (r.max !== undefined &&
|
|
351
|
-
(typeof r.max !== "number" || !Number.isInteger(r.max) || r.max < 1)) {
|
|
352
|
-
throw new Error(`Workflow step ${step.id} retry.max must be a positive integer`);
|
|
353
|
-
}
|
|
354
|
-
if (r.backoff !== undefined && r.backoff !== "fixed" && r.backoff !== "exponential") {
|
|
355
|
-
throw new Error(`Workflow step ${step.id} retry.backoff must be "fixed" or "exponential"`);
|
|
356
|
-
}
|
|
357
|
-
if (r.delay_ms !== undefined &&
|
|
358
|
-
(typeof r.delay_ms !== "number" || !Number.isFinite(r.delay_ms) || r.delay_ms < 0)) {
|
|
359
|
-
throw new Error(`Workflow step ${step.id} retry.delay_ms must be a finite non-negative number`);
|
|
360
|
-
}
|
|
361
|
-
if (r.max_delay_ms !== undefined &&
|
|
362
|
-
(typeof r.max_delay_ms !== "number" ||
|
|
363
|
-
!Number.isFinite(r.max_delay_ms) ||
|
|
364
|
-
r.max_delay_ms < 0)) {
|
|
365
|
-
throw new Error(`Workflow step ${step.id} retry.max_delay_ms must be a finite non-negative number`);
|
|
366
|
-
}
|
|
367
|
-
if (r.jitter !== undefined && typeof r.jitter !== "boolean") {
|
|
368
|
-
throw new Error(`Workflow step ${step.id} retry.jitter must be a boolean`);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (seen.has(step.id)) {
|
|
372
|
-
throw new Error(`Duplicate workflow step id: ${step.id}`);
|
|
373
|
-
}
|
|
374
|
-
if (isParallel) {
|
|
375
|
-
const parallel = step.parallel;
|
|
376
|
-
for (const branch of parallel.branches) {
|
|
377
|
-
seen.add(branch.id);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
seen.add(step.id);
|
|
381
|
-
}
|
|
382
|
-
return parsed;
|
|
383
|
-
}
|
|
384
66
|
export function resolveWorkflowArgs(argDefs, provided) {
|
|
385
67
|
const resolved = createRecord();
|
|
386
68
|
if (argDefs) {
|
|
@@ -879,7 +561,7 @@ export async function runWorkflowFile({ filePath, args, ctx, resume, approved, r
|
|
|
879
561
|
return buffered;
|
|
880
562
|
};
|
|
881
563
|
const runBranch = async (branch) => {
|
|
882
|
-
const mergedBranchEnv = { ...
|
|
564
|
+
const mergedBranchEnv = { ...step.env, ...branch.env };
|
|
883
565
|
const branchEnv = mergeEnv(ctx.env, workflow.env, mergedBranchEnv, resolvedArgs, results);
|
|
884
566
|
const branchCwd = resolveCwd(branch.cwd ?? step.cwd ?? workflow.cwd, resolvedArgs) ?? ctx.cwd;
|
|
885
567
|
const branchShell = typeof branch.run === "string" ? branch.run : branch.command;
|
|
@@ -1433,262 +1115,6 @@ export async function runWorkflowFile({ filePath, args, ctx, resume, approved, r
|
|
|
1433
1115
|
// output. Because dry-run placeholders have no actual stdout/json, we surface
|
|
1434
1116
|
// this so users know the value is unknown at plan time rather than silently
|
|
1435
1117
|
// resolving to an empty string.
|
|
1436
|
-
function dryRunStdinNote(stdin) {
|
|
1437
|
-
if (stdin === null || stdin === undefined)
|
|
1438
|
-
return null;
|
|
1439
|
-
if (typeof stdin !== "string")
|
|
1440
|
-
return null;
|
|
1441
|
-
const trimmed = stdin.trim();
|
|
1442
|
-
// Strict step ref: '$step-id.stdout' or '$step-id.json'
|
|
1443
|
-
if (/^\$[A-Za-z0-9_-]+\.(stdout|json)$/.test(trimmed)) {
|
|
1444
|
-
return `${trimmed} [output unknown at plan time]`;
|
|
1445
|
-
}
|
|
1446
|
-
// Inline template ref: contains '$stepid.stdout' or '$stepid.json'
|
|
1447
|
-
if (/\$[A-Za-z0-9_-]+\.(stdout|json)/.test(trimmed)) {
|
|
1448
|
-
return `${trimmed} [contains step output refs — unknown at plan time]`;
|
|
1449
|
-
}
|
|
1450
|
-
return null;
|
|
1451
|
-
}
|
|
1452
|
-
function dryRunTemplateNote(input) {
|
|
1453
|
-
if (/\$[A-Za-z0-9_-]+\.(stdout|json)/.test(input)) {
|
|
1454
|
-
return "[contains step output refs — unknown at plan time]";
|
|
1455
|
-
}
|
|
1456
|
-
return null;
|
|
1457
|
-
}
|
|
1458
|
-
function hasDeferredDryRunStageName(input) {
|
|
1459
|
-
return /\$[A-Za-z0-9_-]+\.(stdout|json)/.test(input);
|
|
1460
|
-
}
|
|
1461
|
-
function resolveDryRunTemplate(input, args, results) {
|
|
1462
|
-
const withArgs = resolveArgsTemplate(input, args);
|
|
1463
|
-
return withArgs.replace(/\$([A-Za-z0-9_-]+)\.(stdout|json|approved)/g, (match, id, field) => {
|
|
1464
|
-
if (field === "approved") {
|
|
1465
|
-
const step = results[id];
|
|
1466
|
-
if (!step)
|
|
1467
|
-
return match;
|
|
1468
|
-
return step.approved === true ? "true" : "false";
|
|
1469
|
-
}
|
|
1470
|
-
return match;
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1473
|
-
function dryRunWorkflow({ steps, resolvedArgs, results, startIndex, ctx, }) {
|
|
1474
|
-
const lines = [];
|
|
1475
|
-
const totalSteps = steps.length - startIndex;
|
|
1476
|
-
lines.push(`[DRY RUN] Would execute ${totalSteps} step${totalSteps !== 1 ? "s" : ""}:\n`);
|
|
1477
|
-
for (let idx = startIndex; idx < steps.length; idx++) {
|
|
1478
|
-
const step = steps[idx];
|
|
1479
|
-
const num = idx - startIndex + 1;
|
|
1480
|
-
if (!evaluateCondition(step.when ?? step.condition, results)) {
|
|
1481
|
-
results[step.id] = { id: step.id, skipped: true };
|
|
1482
|
-
lines.push(` ${num}. ${step.id} [skipped — condition: false]`);
|
|
1483
|
-
continue;
|
|
1484
|
-
}
|
|
1485
|
-
if (isInputStep(step.input)) {
|
|
1486
|
-
lines.push(` ${num}. ${step.id} [input]`);
|
|
1487
|
-
lines.push(` prompt: ${step.input.prompt}`);
|
|
1488
|
-
lines.push(` [input required]`);
|
|
1489
|
-
results[step.id] = { id: step.id, response: { pending: true } };
|
|
1490
|
-
continue;
|
|
1491
|
-
}
|
|
1492
|
-
if (typeof step.for_each === "string" && Array.isArray(step.steps)) {
|
|
1493
|
-
lines.push(` ${num}. ${step.id} [for_each]`);
|
|
1494
|
-
const forEachRef = step.for_each;
|
|
1495
|
-
const forEachNote = dryRunTemplateNote(forEachRef);
|
|
1496
|
-
lines.push(` for_each: ${forEachRef}${forEachNote ? ` ${forEachNote}` : ""}`);
|
|
1497
|
-
if (forEachRef.trim().startsWith("$")) {
|
|
1498
|
-
try {
|
|
1499
|
-
resolveInputValue(forEachRef, resolvedArgs, results);
|
|
1500
|
-
}
|
|
1501
|
-
catch (err) {
|
|
1502
|
-
throw new Error(`Workflow step ${step.id} for_each: ${err?.message ?? String(err)}`);
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
const dryItemVar = step.item_var ?? "item";
|
|
1506
|
-
const dryIndexVar = step.index_var ?? "index";
|
|
1507
|
-
lines.push(` item_var: ${dryItemVar}, index_var: ${dryIndexVar}`);
|
|
1508
|
-
if (step.batch_size)
|
|
1509
|
-
lines.push(` batch_size: ${step.batch_size}`);
|
|
1510
|
-
if (step.pause_ms)
|
|
1511
|
-
lines.push(` pause_ms: ${step.pause_ms}`);
|
|
1512
|
-
lines.push(` sub-steps: ${step.steps.length}`);
|
|
1513
|
-
const loopScopedResults = createRecord(results);
|
|
1514
|
-
loopScopedResults[dryItemVar] = { id: dryItemVar, json: { _placeholder: true } };
|
|
1515
|
-
loopScopedResults[dryIndexVar] = { id: dryIndexVar, json: 0 };
|
|
1516
|
-
for (let subIdx = 0; subIdx < step.steps.length; subIdx++) {
|
|
1517
|
-
const sub = step.steps[subIdx];
|
|
1518
|
-
if (!evaluateCondition(sub.when ?? sub.condition, loopScopedResults)) {
|
|
1519
|
-
lines.push(` ${subIdx + 1}. ${sub.id} [skipped — condition: false]`);
|
|
1520
|
-
loopScopedResults[sub.id] = { id: sub.id, skipped: true };
|
|
1521
|
-
continue;
|
|
1522
|
-
}
|
|
1523
|
-
if (sub.stdin !== undefined && sub.stdin !== null) {
|
|
1524
|
-
try {
|
|
1525
|
-
resolveInputValue(sub.stdin, resolvedArgs, loopScopedResults);
|
|
1526
|
-
}
|
|
1527
|
-
catch (err) {
|
|
1528
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} stdin: ${err?.message ?? String(err)}`);
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
const subExec = getStepExecution(sub);
|
|
1532
|
-
if (subExec.kind === "shell") {
|
|
1533
|
-
const command = resolveDryRunTemplate(subExec.value, resolvedArgs, loopScopedResults);
|
|
1534
|
-
lines.push(` ${subIdx + 1}. ${sub.id} [shell] run: ${command}`);
|
|
1535
|
-
}
|
|
1536
|
-
else if (subExec.kind === "pipeline") {
|
|
1537
|
-
if (!ctx.registry) {
|
|
1538
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} requires a command registry for pipeline execution`);
|
|
1539
|
-
}
|
|
1540
|
-
const pipelineText = resolveDryRunTemplate(subExec.value, resolvedArgs, loopScopedResults);
|
|
1541
|
-
const stages = parsePipeline(pipelineText);
|
|
1542
|
-
for (const stage of stages) {
|
|
1543
|
-
if (hasDeferredDryRunStageName(stage.name))
|
|
1544
|
-
continue;
|
|
1545
|
-
if (!ctx.registry.get(stage.name)) {
|
|
1546
|
-
throw new Error(`Workflow step ${step.id} for_each sub-step ${sub.id} pipeline: unknown command: ${stage.name}`);
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
lines.push(` ${subIdx + 1}. ${sub.id} [pipeline] pipeline: ${pipelineText}`);
|
|
1550
|
-
}
|
|
1551
|
-
else {
|
|
1552
|
-
lines.push(` ${subIdx + 1}. ${sub.id} [no-op]`);
|
|
1553
|
-
}
|
|
1554
|
-
loopScopedResults[sub.id] = { id: sub.id };
|
|
1555
|
-
}
|
|
1556
|
-
if (step.timeout_ms)
|
|
1557
|
-
lines.push(` timeout: ${step.timeout_ms}ms`);
|
|
1558
|
-
if (step.on_error && step.on_error !== "stop")
|
|
1559
|
-
lines.push(` on_error: ${step.on_error}`);
|
|
1560
|
-
if (step.retry && typeof step.retry === "object") {
|
|
1561
|
-
const rc = resolveRetryConfig(step.retry);
|
|
1562
|
-
if (rc.max > 1) {
|
|
1563
|
-
lines.push(` retry: up to ${rc.max} attempts, ${rc.backoff} backoff (base: ${rc.delay_ms}ms${rc.jitter ? ", jitter" : ""})`);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
results[step.id] = { id: step.id };
|
|
1567
|
-
continue;
|
|
1568
|
-
}
|
|
1569
|
-
// Validate stdin refs early — throws if a strict ref like '$missing.stdout'
|
|
1570
|
-
// points to a step that doesn't exist at all (real execution would also fail).
|
|
1571
|
-
// We call resolveInputValue with the current results so refs to steps we've
|
|
1572
|
-
// already visited (placeholders) are accepted without throwing.
|
|
1573
|
-
if (step.stdin !== undefined && step.stdin !== null) {
|
|
1574
|
-
try {
|
|
1575
|
-
resolveInputValue(step.stdin, resolvedArgs, results);
|
|
1576
|
-
}
|
|
1577
|
-
catch (err) {
|
|
1578
|
-
throw new Error(`Workflow step ${step.id} stdin: ${err?.message ?? String(err)}`);
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
const execution = getStepExecution(step);
|
|
1582
|
-
// Annotate when the resolved command/pipeline references a prior step's output.
|
|
1583
|
-
// Since dry-run placeholders have no actual stdout/json, note it explicitly
|
|
1584
|
-
// rather than silently collapsing the reference to an empty string.
|
|
1585
|
-
const stdinNote = dryRunStdinNote(step.stdin);
|
|
1586
|
-
if (execution.kind === "parallel") {
|
|
1587
|
-
lines.push(` ${num}. ${step.id} [parallel]`);
|
|
1588
|
-
lines.push(` wait: ${step.parallel?.wait ?? "all"}`);
|
|
1589
|
-
if (step.parallel?.timeout_ms) {
|
|
1590
|
-
lines.push(` timeout: ${step.parallel.timeout_ms}ms`);
|
|
1591
|
-
}
|
|
1592
|
-
for (const branch of step.parallel?.branches ?? []) {
|
|
1593
|
-
const branchShell = typeof branch.run === "string" ? branch.run : branch.command;
|
|
1594
|
-
if (typeof branch.pipeline === "string" && branch.pipeline.trim()) {
|
|
1595
|
-
const pipelineText = resolveDryRunTemplate(branch.pipeline, resolvedArgs, results);
|
|
1596
|
-
const pipelineNote = dryRunTemplateNote(pipelineText);
|
|
1597
|
-
if (!ctx.registry) {
|
|
1598
|
-
throw new Error(`Parallel branch ${branch.id} requires a command registry for pipeline execution`);
|
|
1599
|
-
}
|
|
1600
|
-
const stages = parsePipeline(pipelineText);
|
|
1601
|
-
for (const stage of stages) {
|
|
1602
|
-
if (hasDeferredDryRunStageName(stage.name))
|
|
1603
|
-
continue;
|
|
1604
|
-
if (!ctx.registry.get(stage.name)) {
|
|
1605
|
-
throw new Error(`Parallel branch ${branch.id} pipeline references unknown command: ${stage.name}`);
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
lines.push(` branch ${branch.id}: [pipeline] ${pipelineText}${pipelineNote ? ` ${pipelineNote}` : ""}`);
|
|
1609
|
-
}
|
|
1610
|
-
else if (typeof branchShell === "string" && branchShell.trim()) {
|
|
1611
|
-
const command = resolveDryRunTemplate(branchShell, resolvedArgs, results);
|
|
1612
|
-
const commandNote = dryRunTemplateNote(command);
|
|
1613
|
-
lines.push(` branch ${branch.id}: [shell] ${command}${commandNote ? ` ${commandNote}` : ""}`);
|
|
1614
|
-
}
|
|
1615
|
-
else {
|
|
1616
|
-
lines.push(` branch ${branch.id}: [no-op]`);
|
|
1617
|
-
}
|
|
1618
|
-
results[branch.id] = { id: branch.id };
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
else if (execution.kind === "workflow") {
|
|
1622
|
-
const workflowPath = resolveDryRunTemplate(execution.value, resolvedArgs, results);
|
|
1623
|
-
const pathNote = dryRunTemplateNote(workflowPath);
|
|
1624
|
-
lines.push(` ${num}. ${step.id} [workflow]`);
|
|
1625
|
-
lines.push(` workflow: ${workflowPath}${pathNote ? ` ${pathNote}` : ""}`);
|
|
1626
|
-
if (step.workflow_args && typeof step.workflow_args === "object") {
|
|
1627
|
-
const argKeys = Object.keys(step.workflow_args);
|
|
1628
|
-
if (argKeys.length) {
|
|
1629
|
-
lines.push(` args: ${argKeys.join(", ")}`);
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
else if (execution.kind === "shell") {
|
|
1634
|
-
const command = resolveDryRunTemplate(execution.value, resolvedArgs, results);
|
|
1635
|
-
const commandNote = dryRunTemplateNote(command);
|
|
1636
|
-
lines.push(` ${num}. ${step.id} [shell]`);
|
|
1637
|
-
lines.push(` run: ${command}${commandNote ? ` ${commandNote}` : ""}`);
|
|
1638
|
-
}
|
|
1639
|
-
else if (execution.kind === "pipeline") {
|
|
1640
|
-
const pipelineText = resolveDryRunTemplate(execution.value, resolvedArgs, results);
|
|
1641
|
-
const pipelineNote = dryRunTemplateNote(pipelineText);
|
|
1642
|
-
// Validate pipeline syntax and registry even in dry-run so errors surface early.
|
|
1643
|
-
if (!ctx.registry) {
|
|
1644
|
-
throw new Error(`Workflow step ${step.id} requires a command registry for pipeline execution`);
|
|
1645
|
-
}
|
|
1646
|
-
// Validate that every stage name is a known command.
|
|
1647
|
-
const stages = parsePipeline(pipelineText);
|
|
1648
|
-
for (const stage of stages) {
|
|
1649
|
-
if (hasDeferredDryRunStageName(stage.name)) {
|
|
1650
|
-
continue;
|
|
1651
|
-
}
|
|
1652
|
-
if (!ctx.registry.get(stage.name)) {
|
|
1653
|
-
throw new Error(`Workflow step ${step.id} pipeline references unknown command: ${stage.name}`);
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
lines.push(` ${num}. ${step.id} [pipeline]`);
|
|
1657
|
-
lines.push(` pipeline: ${pipelineText}${pipelineNote ? ` ${pipelineNote}` : ""}`);
|
|
1658
|
-
if (stages.some((stage) => hasDeferredDryRunStageName(stage.name))) {
|
|
1659
|
-
lines.push(" [command validation deferred — stage name depends on step output]");
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
else {
|
|
1663
|
-
lines.push(` ${num}. ${step.id} [no-op]`);
|
|
1664
|
-
}
|
|
1665
|
-
if (stdinNote)
|
|
1666
|
-
lines.push(` stdin: ${stdinNote}`);
|
|
1667
|
-
if (step.timeout_ms)
|
|
1668
|
-
lines.push(` timeout: ${step.timeout_ms}ms`);
|
|
1669
|
-
if (step.on_error && step.on_error !== "stop")
|
|
1670
|
-
lines.push(` on_error: ${step.on_error}`);
|
|
1671
|
-
if (step.retry && typeof step.retry === "object") {
|
|
1672
|
-
const rc = resolveRetryConfig(step.retry);
|
|
1673
|
-
if (rc.max > 1) {
|
|
1674
|
-
lines.push(` retry: up to ${rc.max} attempts, ${rc.backoff} backoff (base: ${rc.delay_ms}ms${rc.jitter ? ", jitter" : ""})`);
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
if (isApprovalStep(step.approval)) {
|
|
1678
|
-
lines.push(` [approval required]`);
|
|
1679
|
-
}
|
|
1680
|
-
// Record a placeholder result so later steps can reference this step in conditions.
|
|
1681
|
-
// For approval steps, model approval as granted so downstream conditions like
|
|
1682
|
-
// $step.approved evaluate correctly in the plan (rather than always being false).
|
|
1683
|
-
// We intentionally omit stdout/json — dryRunStdinNote() surfaces that gap.
|
|
1684
|
-
results[step.id] = isApprovalStep(step.approval)
|
|
1685
|
-
? { id: step.id, approved: true }
|
|
1686
|
-
: { id: step.id };
|
|
1687
|
-
}
|
|
1688
|
-
lines.push("");
|
|
1689
|
-
ctx.stderr.write(lines.join("\n"));
|
|
1690
|
-
return { status: "ok", output: [] };
|
|
1691
|
-
}
|
|
1692
1118
|
export function decodeWorkflowResumePayload(payload) {
|
|
1693
1119
|
if (!payload || typeof payload !== "object")
|
|
1694
1120
|
return null;
|
|
@@ -1894,148 +1320,6 @@ function validateWorkflowPipelineShape(pipeline) {
|
|
|
1894
1320
|
throw new Error("Invalid workflow resume state");
|
|
1895
1321
|
}
|
|
1896
1322
|
}
|
|
1897
|
-
function mergeEnv(base, workflowEnv, stepEnv, args, results) {
|
|
1898
|
-
const env = createRecord(base);
|
|
1899
|
-
// Expose resolved args as env vars so shell commands can safely reference them
|
|
1900
|
-
// without embedding raw values into the command string.
|
|
1901
|
-
// Example: $LOBSTER_ARG_TEXT
|
|
1902
|
-
env.LOBSTER_ARGS_JSON = JSON.stringify(args ?? {});
|
|
1903
|
-
for (const [key, value] of Object.entries(args ?? {})) {
|
|
1904
|
-
const normalized = normalizeArgEnvKey(key);
|
|
1905
|
-
if (!normalized)
|
|
1906
|
-
continue;
|
|
1907
|
-
env[`LOBSTER_ARG_${normalized}`] = String(value);
|
|
1908
|
-
}
|
|
1909
|
-
const apply = (source) => {
|
|
1910
|
-
if (!source)
|
|
1911
|
-
return;
|
|
1912
|
-
for (const [key, value] of Object.entries(source)) {
|
|
1913
|
-
if (typeof value === "string") {
|
|
1914
|
-
env[key] = resolveTemplate(value, args, results);
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
};
|
|
1918
|
-
// Allow explicit env blocks to override injected defaults.
|
|
1919
|
-
apply(workflowEnv);
|
|
1920
|
-
apply(stepEnv);
|
|
1921
|
-
return env;
|
|
1922
|
-
}
|
|
1923
|
-
function normalizeArgEnvKey(key) {
|
|
1924
|
-
const trimmed = String(key ?? "").trim();
|
|
1925
|
-
if (!trimmed)
|
|
1926
|
-
return null;
|
|
1927
|
-
// Keep it predictable for shells: uppercase and [A-Z0-9_]
|
|
1928
|
-
const up = trimmed.toUpperCase();
|
|
1929
|
-
let normalized = up.replace(/[^A-Z0-9]+/g, "_");
|
|
1930
|
-
let start = 0;
|
|
1931
|
-
while (normalized[start] === "_")
|
|
1932
|
-
start++;
|
|
1933
|
-
let end = normalized.length;
|
|
1934
|
-
while (end > start && normalized[end - 1] === "_")
|
|
1935
|
-
end--;
|
|
1936
|
-
normalized = normalized.slice(start, end);
|
|
1937
|
-
return normalized || null;
|
|
1938
|
-
}
|
|
1939
|
-
function resolveCwd(cwd, args) {
|
|
1940
|
-
if (!cwd)
|
|
1941
|
-
return undefined;
|
|
1942
|
-
return resolveArgsTemplate(cwd, args);
|
|
1943
|
-
}
|
|
1944
|
-
function resolveInputValue(stdin, args, results) {
|
|
1945
|
-
if (stdin === null || stdin === undefined)
|
|
1946
|
-
return null;
|
|
1947
|
-
if (typeof stdin === "string") {
|
|
1948
|
-
const ref = parseStepRef(stdin.trim());
|
|
1949
|
-
if (ref)
|
|
1950
|
-
return getStepRefValue(ref, results, true);
|
|
1951
|
-
return resolveTemplate(stdin, args, results);
|
|
1952
|
-
}
|
|
1953
|
-
return stdin;
|
|
1954
|
-
}
|
|
1955
|
-
function resolveShellStdin(stdin, args, results) {
|
|
1956
|
-
const value = resolveInputValue(stdin, args, results);
|
|
1957
|
-
return encodeShellInput(value);
|
|
1958
|
-
}
|
|
1959
|
-
function resolveTemplate(input, args, results) {
|
|
1960
|
-
const withArgs = resolveArgsTemplate(input, args);
|
|
1961
|
-
return resolveStepRefs(withArgs, results);
|
|
1962
|
-
}
|
|
1963
|
-
function resolveWorkflowStepArgs(workflowArgs, parentArgs, results) {
|
|
1964
|
-
if (!workflowArgs)
|
|
1965
|
-
return createRecord();
|
|
1966
|
-
const resolved = createRecord();
|
|
1967
|
-
for (const [key, value] of Object.entries(workflowArgs)) {
|
|
1968
|
-
if (typeof value === "string") {
|
|
1969
|
-
resolved[key] = resolveTemplate(value, parentArgs, results);
|
|
1970
|
-
}
|
|
1971
|
-
else {
|
|
1972
|
-
resolved[key] = value;
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
return resolved;
|
|
1976
|
-
}
|
|
1977
|
-
function resolveArgsTemplate(input, args) {
|
|
1978
|
-
return input.replace(/\$\{([A-Za-z0-9_-]+)\}/g, (match, key) => {
|
|
1979
|
-
if (Object.hasOwn(args, key))
|
|
1980
|
-
return String(args[key]);
|
|
1981
|
-
return match;
|
|
1982
|
-
});
|
|
1983
|
-
}
|
|
1984
|
-
function resolveStepRefs(input, results) {
|
|
1985
|
-
return input.replace(/\$([A-Za-z0-9_-]+)\.([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)/g, (match, id, pathValue) => {
|
|
1986
|
-
if (!(id in results)) {
|
|
1987
|
-
return match;
|
|
1988
|
-
}
|
|
1989
|
-
const refValue = getStepRefValue({ id, path: pathValue }, results, false);
|
|
1990
|
-
if (refValue === undefined) {
|
|
1991
|
-
if (pathValue === "approved" || pathValue === "skipped")
|
|
1992
|
-
return "false";
|
|
1993
|
-
return "";
|
|
1994
|
-
}
|
|
1995
|
-
return renderTemplateValue(refValue);
|
|
1996
|
-
});
|
|
1997
|
-
}
|
|
1998
|
-
function parseStepRef(value) {
|
|
1999
|
-
const match = value.match(/^\$([A-Za-z0-9_-]+)\.([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)$/);
|
|
2000
|
-
if (!match)
|
|
2001
|
-
return null;
|
|
2002
|
-
return { id: match[1], path: match[2] };
|
|
2003
|
-
}
|
|
2004
|
-
function getStepRefValue(ref, results, strict) {
|
|
2005
|
-
const step = results[ref.id];
|
|
2006
|
-
if (!step) {
|
|
2007
|
-
if (strict)
|
|
2008
|
-
throw new Error(`Unknown step reference: ${ref.id}.${ref.path}`);
|
|
2009
|
-
return undefined;
|
|
2010
|
-
}
|
|
2011
|
-
return getValueByPath(step, ref.path);
|
|
2012
|
-
}
|
|
2013
|
-
function evaluateCondition(condition, results) {
|
|
2014
|
-
if (condition === undefined || condition === null)
|
|
2015
|
-
return true;
|
|
2016
|
-
if (typeof condition === "boolean")
|
|
2017
|
-
return condition;
|
|
2018
|
-
if (typeof condition !== "string")
|
|
2019
|
-
throw new Error("Unsupported condition type");
|
|
2020
|
-
const trimmed = condition.trim();
|
|
2021
|
-
if (trimmed === "true")
|
|
2022
|
-
return true;
|
|
2023
|
-
if (trimmed === "false")
|
|
2024
|
-
return false;
|
|
2025
|
-
return evaluateConditionExpression(trimmed, results);
|
|
2026
|
-
}
|
|
2027
|
-
function isApprovalStep(approval) {
|
|
2028
|
-
if (approval === true)
|
|
2029
|
-
return true;
|
|
2030
|
-
if (typeof approval === "string" && approval.trim().length > 0)
|
|
2031
|
-
return true;
|
|
2032
|
-
if (approval && typeof approval === "object" && !Array.isArray(approval))
|
|
2033
|
-
return true;
|
|
2034
|
-
return false;
|
|
2035
|
-
}
|
|
2036
|
-
function isInputStep(input) {
|
|
2037
|
-
return Boolean(input && typeof input === "object" && !Array.isArray(input));
|
|
2038
|
-
}
|
|
2039
1323
|
function extractApprovalRequest(step, result, env) {
|
|
2040
1324
|
const approvalConfig = normalizeApprovalConfig(step.approval);
|
|
2041
1325
|
const configIdentity = approvalIdentityFromRaw(approvalConfig);
|
|
@@ -2310,6 +1594,8 @@ function trackStepCost(costTracker, stepId, result, llmSpendLedger, deferredChar
|
|
|
2310
1594
|
// charge restored from resume state written before it carried a cost falls back.
|
|
2311
1595
|
if (settled?.usage) {
|
|
2312
1596
|
costTracker.recordUsage(stepId, settled.model ?? null, settled.usage);
|
|
1597
|
+
markLlmProvenanceReplayed(record);
|
|
1598
|
+
markLlmProvenanceReplayed(usage);
|
|
2313
1599
|
continue;
|
|
2314
1600
|
}
|
|
2315
1601
|
}
|
|
@@ -2330,6 +1616,8 @@ function trackStepCost(costTracker, stepId, result, llmSpendLedger, deferredChar
|
|
|
2330
1616
|
continue;
|
|
2331
1617
|
}
|
|
2332
1618
|
costTracker.recordUsage(stepId, model, usage);
|
|
1619
|
+
markLlmProvenanceReplayed(record);
|
|
1620
|
+
markLlmProvenanceReplayed(usage);
|
|
2333
1621
|
}
|
|
2334
1622
|
if (!deferredCharges)
|
|
2335
1623
|
settleDeferredCosts(costTracker, deferred, llmSpendLedger);
|
|
@@ -2537,295 +1825,6 @@ function estimateSerializedBytes(value) {
|
|
|
2537
1825
|
return Number.POSITIVE_INFINITY;
|
|
2538
1826
|
}
|
|
2539
1827
|
}
|
|
2540
|
-
function renderTemplateValue(value) {
|
|
2541
|
-
if (value === undefined || value === null)
|
|
2542
|
-
return "";
|
|
2543
|
-
if (typeof value === "string")
|
|
2544
|
-
return value;
|
|
2545
|
-
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
2546
|
-
return String(value);
|
|
2547
|
-
}
|
|
2548
|
-
try {
|
|
2549
|
-
return JSON.stringify(value);
|
|
2550
|
-
}
|
|
2551
|
-
catch {
|
|
2552
|
-
return String(value);
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
function getValueByPath(value, pathValue) {
|
|
2556
|
-
const fields = pathValue.split(".");
|
|
2557
|
-
let current = value;
|
|
2558
|
-
for (const field of fields) {
|
|
2559
|
-
if (current === null || current === undefined)
|
|
2560
|
-
return undefined;
|
|
2561
|
-
if (Array.isArray(current)) {
|
|
2562
|
-
const idx = Number(field);
|
|
2563
|
-
if (!Number.isInteger(idx) || idx < 0 || idx >= current.length)
|
|
2564
|
-
return undefined;
|
|
2565
|
-
current = current[idx];
|
|
2566
|
-
continue;
|
|
2567
|
-
}
|
|
2568
|
-
if (typeof current !== "object")
|
|
2569
|
-
return undefined;
|
|
2570
|
-
current = current[field];
|
|
2571
|
-
}
|
|
2572
|
-
return current;
|
|
2573
|
-
}
|
|
2574
|
-
function evaluateConditionExpression(expression, results) {
|
|
2575
|
-
const tokens = tokenizeCondition(expression);
|
|
2576
|
-
if (tokens.length === 0) {
|
|
2577
|
-
throw new Error(`Unsupported condition: ${expression}`);
|
|
2578
|
-
}
|
|
2579
|
-
let index = 0;
|
|
2580
|
-
function parseOr() {
|
|
2581
|
-
let left = parseAnd();
|
|
2582
|
-
while (match("or")) {
|
|
2583
|
-
const right = parseAnd();
|
|
2584
|
-
left = Boolean(left) || Boolean(right);
|
|
2585
|
-
}
|
|
2586
|
-
return left;
|
|
2587
|
-
}
|
|
2588
|
-
function parseAnd() {
|
|
2589
|
-
let left = parseEquality();
|
|
2590
|
-
while (match("and")) {
|
|
2591
|
-
const right = parseEquality();
|
|
2592
|
-
left = Boolean(left) && Boolean(right);
|
|
2593
|
-
}
|
|
2594
|
-
return left;
|
|
2595
|
-
}
|
|
2596
|
-
function parseEquality() {
|
|
2597
|
-
const left = parseUnary(false);
|
|
2598
|
-
if (match("eq")) {
|
|
2599
|
-
return compareConditionValues(left, parseUnary(true));
|
|
2600
|
-
}
|
|
2601
|
-
if (match("neq")) {
|
|
2602
|
-
return !compareConditionValues(left, parseUnary(true));
|
|
2603
|
-
}
|
|
2604
|
-
if (match("lt")) {
|
|
2605
|
-
return numericCompare(left, parseUnary(true), (a, b) => a < b);
|
|
2606
|
-
}
|
|
2607
|
-
if (match("lte")) {
|
|
2608
|
-
return numericCompare(left, parseUnary(true), (a, b) => a <= b);
|
|
2609
|
-
}
|
|
2610
|
-
if (match("gt")) {
|
|
2611
|
-
return numericCompare(left, parseUnary(true), (a, b) => a > b);
|
|
2612
|
-
}
|
|
2613
|
-
if (match("gte")) {
|
|
2614
|
-
return numericCompare(left, parseUnary(true), (a, b) => a >= b);
|
|
2615
|
-
}
|
|
2616
|
-
return left;
|
|
2617
|
-
}
|
|
2618
|
-
function parseUnary(allowBareIdentifier) {
|
|
2619
|
-
if (match("not")) {
|
|
2620
|
-
return !Boolean(parseUnary(allowBareIdentifier));
|
|
2621
|
-
}
|
|
2622
|
-
return parsePrimary(allowBareIdentifier);
|
|
2623
|
-
}
|
|
2624
|
-
function parsePrimary(allowBareIdentifier) {
|
|
2625
|
-
const token = tokens[index];
|
|
2626
|
-
if (!token) {
|
|
2627
|
-
throw new Error(`Unsupported condition: ${expression}`);
|
|
2628
|
-
}
|
|
2629
|
-
index += 1;
|
|
2630
|
-
if (token.type === "lparen") {
|
|
2631
|
-
const value = parseOr();
|
|
2632
|
-
expect("rparen");
|
|
2633
|
-
return value;
|
|
2634
|
-
}
|
|
2635
|
-
if (token.type === "step_ref") {
|
|
2636
|
-
return getStepRefValue(token.value, results, true);
|
|
2637
|
-
}
|
|
2638
|
-
if (token.type === "string" ||
|
|
2639
|
-
token.type === "number" ||
|
|
2640
|
-
token.type === "boolean" ||
|
|
2641
|
-
token.type === "null") {
|
|
2642
|
-
return token.value;
|
|
2643
|
-
}
|
|
2644
|
-
if (token.type === "identifier" && allowBareIdentifier) {
|
|
2645
|
-
return token.value;
|
|
2646
|
-
}
|
|
2647
|
-
throw new Error(`Unsupported condition: ${expression}`);
|
|
2648
|
-
}
|
|
2649
|
-
function match(type) {
|
|
2650
|
-
if (tokens[index]?.type !== type)
|
|
2651
|
-
return false;
|
|
2652
|
-
index += 1;
|
|
2653
|
-
return true;
|
|
2654
|
-
}
|
|
2655
|
-
function expect(type) {
|
|
2656
|
-
if (!match(type)) {
|
|
2657
|
-
throw new Error(`Unsupported condition: ${expression}`);
|
|
2658
|
-
}
|
|
2659
|
-
}
|
|
2660
|
-
const value = parseOr();
|
|
2661
|
-
if (index !== tokens.length) {
|
|
2662
|
-
throw new Error(`Unsupported condition: ${expression}`);
|
|
2663
|
-
}
|
|
2664
|
-
return Boolean(value);
|
|
2665
|
-
}
|
|
2666
|
-
function compareConditionValues(left, right) {
|
|
2667
|
-
if (Array.isArray(left) ||
|
|
2668
|
-
Array.isArray(right) ||
|
|
2669
|
-
isPlainConditionObject(left) ||
|
|
2670
|
-
isPlainConditionObject(right)) {
|
|
2671
|
-
return isDeepStrictEqual(left, right);
|
|
2672
|
-
}
|
|
2673
|
-
return Object.is(left, right);
|
|
2674
|
-
}
|
|
2675
|
-
function isStrictlyNumeric(value) {
|
|
2676
|
-
if (typeof value === "number")
|
|
2677
|
-
return !Number.isNaN(value);
|
|
2678
|
-
if (typeof value === "string")
|
|
2679
|
-
return value.trim() !== "" && !Number.isNaN(Number(value));
|
|
2680
|
-
return false;
|
|
2681
|
-
}
|
|
2682
|
-
function numericCompare(left, right, cmp) {
|
|
2683
|
-
if (!isStrictlyNumeric(left) || !isStrictlyNumeric(right))
|
|
2684
|
-
return false;
|
|
2685
|
-
return cmp(Number(left), Number(right));
|
|
2686
|
-
}
|
|
2687
|
-
function isPlainConditionObject(value) {
|
|
2688
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2689
|
-
}
|
|
2690
|
-
function tokenizeCondition(expression) {
|
|
2691
|
-
const tokens = [];
|
|
2692
|
-
let index = 0;
|
|
2693
|
-
while (index < expression.length) {
|
|
2694
|
-
const ch = expression[index];
|
|
2695
|
-
if (/\s/.test(ch)) {
|
|
2696
|
-
index += 1;
|
|
2697
|
-
continue;
|
|
2698
|
-
}
|
|
2699
|
-
if (ch === "(") {
|
|
2700
|
-
tokens.push({ type: "lparen" });
|
|
2701
|
-
index += 1;
|
|
2702
|
-
continue;
|
|
2703
|
-
}
|
|
2704
|
-
if (ch === ")") {
|
|
2705
|
-
tokens.push({ type: "rparen" });
|
|
2706
|
-
index += 1;
|
|
2707
|
-
continue;
|
|
2708
|
-
}
|
|
2709
|
-
if (expression.startsWith("&&", index)) {
|
|
2710
|
-
tokens.push({ type: "and" });
|
|
2711
|
-
index += 2;
|
|
2712
|
-
continue;
|
|
2713
|
-
}
|
|
2714
|
-
if (expression.startsWith("||", index)) {
|
|
2715
|
-
tokens.push({ type: "or" });
|
|
2716
|
-
index += 2;
|
|
2717
|
-
continue;
|
|
2718
|
-
}
|
|
2719
|
-
if (expression.startsWith("==", index)) {
|
|
2720
|
-
tokens.push({ type: "eq" });
|
|
2721
|
-
index += 2;
|
|
2722
|
-
continue;
|
|
2723
|
-
}
|
|
2724
|
-
if (expression.startsWith("!=", index)) {
|
|
2725
|
-
tokens.push({ type: "neq" });
|
|
2726
|
-
index += 2;
|
|
2727
|
-
continue;
|
|
2728
|
-
}
|
|
2729
|
-
if (expression.startsWith("<=", index)) {
|
|
2730
|
-
tokens.push({ type: "lte" });
|
|
2731
|
-
index += 2;
|
|
2732
|
-
continue;
|
|
2733
|
-
}
|
|
2734
|
-
if (expression.startsWith(">=", index)) {
|
|
2735
|
-
tokens.push({ type: "gte" });
|
|
2736
|
-
index += 2;
|
|
2737
|
-
continue;
|
|
2738
|
-
}
|
|
2739
|
-
if (ch === "<") {
|
|
2740
|
-
tokens.push({ type: "lt" });
|
|
2741
|
-
index += 1;
|
|
2742
|
-
continue;
|
|
2743
|
-
}
|
|
2744
|
-
if (ch === ">") {
|
|
2745
|
-
tokens.push({ type: "gt" });
|
|
2746
|
-
index += 1;
|
|
2747
|
-
continue;
|
|
2748
|
-
}
|
|
2749
|
-
if (ch === "!") {
|
|
2750
|
-
tokens.push({ type: "not" });
|
|
2751
|
-
index += 1;
|
|
2752
|
-
continue;
|
|
2753
|
-
}
|
|
2754
|
-
if (ch === "$") {
|
|
2755
|
-
const matched = matchConditionStepRef(expression, index);
|
|
2756
|
-
if (!matched) {
|
|
2757
|
-
throw new Error(`Unsupported condition: ${expression}`);
|
|
2758
|
-
}
|
|
2759
|
-
tokens.push({ type: "step_ref", value: matched.ref });
|
|
2760
|
-
index = matched.nextIndex;
|
|
2761
|
-
continue;
|
|
2762
|
-
}
|
|
2763
|
-
if (ch === '"' || ch === "'") {
|
|
2764
|
-
const parsed = parseQuotedConditionString(expression, index, ch);
|
|
2765
|
-
tokens.push({ type: "string", value: parsed.value });
|
|
2766
|
-
index = parsed.nextIndex;
|
|
2767
|
-
continue;
|
|
2768
|
-
}
|
|
2769
|
-
const numberMatch = expression.slice(index).match(/^-?\d+(?:\.\d+)?/);
|
|
2770
|
-
if (numberMatch) {
|
|
2771
|
-
tokens.push({ type: "number", value: Number(numberMatch[0]) });
|
|
2772
|
-
index += numberMatch[0].length;
|
|
2773
|
-
continue;
|
|
2774
|
-
}
|
|
2775
|
-
const identMatch = expression.slice(index).match(/^[A-Za-z_][A-Za-z0-9_-]*/);
|
|
2776
|
-
if (identMatch) {
|
|
2777
|
-
const raw = identMatch[0];
|
|
2778
|
-
if (raw === "true") {
|
|
2779
|
-
tokens.push({ type: "boolean", value: true });
|
|
2780
|
-
}
|
|
2781
|
-
else if (raw === "false") {
|
|
2782
|
-
tokens.push({ type: "boolean", value: false });
|
|
2783
|
-
}
|
|
2784
|
-
else if (raw === "null") {
|
|
2785
|
-
tokens.push({ type: "null", value: null });
|
|
2786
|
-
}
|
|
2787
|
-
else {
|
|
2788
|
-
tokens.push({ type: "identifier", value: raw });
|
|
2789
|
-
}
|
|
2790
|
-
index += raw.length;
|
|
2791
|
-
continue;
|
|
2792
|
-
}
|
|
2793
|
-
throw new Error(`Unsupported condition: ${expression}`);
|
|
2794
|
-
}
|
|
2795
|
-
return tokens;
|
|
2796
|
-
}
|
|
2797
|
-
function matchConditionStepRef(expression, startIndex) {
|
|
2798
|
-
const match = expression
|
|
2799
|
-
.slice(startIndex)
|
|
2800
|
-
.match(/^\$([A-Za-z0-9_-]+)\.([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)/);
|
|
2801
|
-
if (!match)
|
|
2802
|
-
return null;
|
|
2803
|
-
return {
|
|
2804
|
-
ref: { id: match[1], path: match[2] },
|
|
2805
|
-
nextIndex: startIndex + match[0].length,
|
|
2806
|
-
};
|
|
2807
|
-
}
|
|
2808
|
-
function parseQuotedConditionString(expression, startIndex, quoteChar) {
|
|
2809
|
-
let value = "";
|
|
2810
|
-
let index = startIndex + 1;
|
|
2811
|
-
while (index < expression.length) {
|
|
2812
|
-
const ch = expression[index];
|
|
2813
|
-
if (ch === "\\") {
|
|
2814
|
-
const next = expression[index + 1];
|
|
2815
|
-
if (next === undefined)
|
|
2816
|
-
break;
|
|
2817
|
-
value += next;
|
|
2818
|
-
index += 2;
|
|
2819
|
-
continue;
|
|
2820
|
-
}
|
|
2821
|
-
if (ch === quoteChar) {
|
|
2822
|
-
return { value, nextIndex: index + 1 };
|
|
2823
|
-
}
|
|
2824
|
-
value += ch;
|
|
2825
|
-
index += 1;
|
|
2826
|
-
}
|
|
2827
|
-
throw new Error(`Unsupported condition: ${expression}`);
|
|
2828
|
-
}
|
|
2829
1828
|
async function runShellCommand({ command, stdin, env, cwd, signal, forceTerminationSignal, killSignal, }) {
|
|
2830
1829
|
signal?.throwIfAborted();
|
|
2831
1830
|
signal?.throwIfAborted();
|
|
@@ -2843,22 +1842,6 @@ async function runShellCommand({ command, stdin, env, cwd, signal, forceTerminat
|
|
|
2843
1842
|
return { stdout, stderr };
|
|
2844
1843
|
throw new Error(`workflow command failed (${code}): ${stderr.trim() || stdout.trim() || command}`);
|
|
2845
1844
|
}
|
|
2846
|
-
function getStepExecution(step) {
|
|
2847
|
-
if (step.parallel && typeof step.parallel === "object" && !Array.isArray(step.parallel)) {
|
|
2848
|
-
return { kind: "parallel", value: step.parallel };
|
|
2849
|
-
}
|
|
2850
|
-
if (typeof step.workflow === "string" && step.workflow.trim()) {
|
|
2851
|
-
return { kind: "workflow", value: step.workflow };
|
|
2852
|
-
}
|
|
2853
|
-
if (typeof step.pipeline === "string" && step.pipeline.trim()) {
|
|
2854
|
-
return { kind: "pipeline", value: step.pipeline };
|
|
2855
|
-
}
|
|
2856
|
-
const shellCommand = typeof step.run === "string" ? step.run : step.command;
|
|
2857
|
-
if (typeof shellCommand === "string" && shellCommand.trim()) {
|
|
2858
|
-
return { kind: "shell", value: shellCommand };
|
|
2859
|
-
}
|
|
2860
|
-
return { kind: "none" };
|
|
2861
|
-
}
|
|
2862
1845
|
async function runPipelineStep({ stepId, pipelineText, inputValue, ctx, llmSpendLedger, env, cwd, resume, requestInputEnabled = true, onExecutionStart, }) {
|
|
2863
1846
|
let pipeline;
|
|
2864
1847
|
try {
|
|
@@ -2955,6 +1938,15 @@ async function runPipelineStep({ stepId, pipelineText, inputValue, ctx, llmSpend
|
|
|
2955
1938
|
};
|
|
2956
1939
|
// Only when a renderer consumed the pipeline: otherwise `json` already is those items.
|
|
2957
1940
|
if (!result.items.length && result.renderedItems.length) {
|
|
1941
|
+
// Only restore marks from the renderer's own matching objects, never from public JSON fields.
|
|
1942
|
+
const source = Array.isArray(json)
|
|
1943
|
+
? result.renderedItems
|
|
1944
|
+
: result.renderedItems.length === 1
|
|
1945
|
+
? result.renderedItems[0]
|
|
1946
|
+
: undefined;
|
|
1947
|
+
if (source !== undefined && stableStringify(source) === stableStringify(json)) {
|
|
1948
|
+
carryLlmProvenance(source, json);
|
|
1949
|
+
}
|
|
2958
1950
|
attachPipelineSourceItems(stepResult, result.renderedItems);
|
|
2959
1951
|
}
|
|
2960
1952
|
return stepResult;
|
|
@@ -2997,13 +1989,6 @@ function abortableSleep(ms, signal) {
|
|
|
2997
1989
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
2998
1990
|
});
|
|
2999
1991
|
}
|
|
3000
|
-
function encodeShellInput(value) {
|
|
3001
|
-
if (value === null || value === undefined)
|
|
3002
|
-
return null;
|
|
3003
|
-
if (typeof value === "string")
|
|
3004
|
-
return value;
|
|
3005
|
-
return JSON.stringify(value);
|
|
3006
|
-
}
|
|
3007
1992
|
function* inputValueToItems(value) {
|
|
3008
1993
|
if (value === null || value === undefined)
|
|
3009
1994
|
return;
|