|
@@ -666,7 +666,8 @@ Result<ExecutionResult> WorkflowEngine::execute(const Workflow& workflow,
|
|
|
node_result.started_at = TimeUtils::nowMs();
|
|
node_result.started_at = TimeUtils::nowMs();
|
|
|
node_result.finished_at = node_result.started_at;
|
|
node_result.finished_at = node_result.started_at;
|
|
|
} else {
|
|
} else {
|
|
|
- node_result = executeNode(evaluated_node, input, result.execution_id, workflow);
|
|
|
|
|
|
|
+ node_result = executeNode(evaluated_node, input, result.execution_id, workflow,
|
|
|
|
|
+ node_def_for_defaults);
|
|
|
}
|
|
}
|
|
|
result.node_results[node_id] = node_result;
|
|
result.node_results[node_id] = node_result;
|
|
|
|
|
|
|
@@ -1119,15 +1120,22 @@ std::vector<std::string> WorkflowEngine::topologicalSort(
|
|
|
NodeExecutionResult WorkflowEngine::executeNode(const WorkflowNode& node,
|
|
NodeExecutionResult WorkflowEngine::executeNode(const WorkflowNode& node,
|
|
|
const nlohmann::json& input,
|
|
const nlohmann::json& input,
|
|
|
const std::string& execution_id,
|
|
const std::string& execution_id,
|
|
|
- const Workflow& workflow) {
|
|
|
|
|
|
|
+ const Workflow& workflow,
|
|
|
|
|
+ const std::optional<NodeDefinition>& prefetched_node_def) {
|
|
|
NodeExecutionResult result;
|
|
NodeExecutionResult result;
|
|
|
result.node_id = node.id;
|
|
result.node_id = node.id;
|
|
|
result.input = input;
|
|
result.input = input;
|
|
|
result.started_at = TimeUtils::nowMs();
|
|
result.started_at = TimeUtils::nowMs();
|
|
|
result.status = NodeStatus::Running;
|
|
result.status = NodeStatus::Running;
|
|
|
|
|
|
|
|
- // Get node definition
|
|
|
|
|
- auto node_def = registry_.getNode(node.type);
|
|
|
|
|
|
|
+ // Get node definition - reuse the caller's lookup if it already made one
|
|
|
|
|
+ // (it did, to apply config defaults before expression evaluation) rather
|
|
|
|
|
+ // than looking it up, and copying its JavaScript source, a second time.
|
|
|
|
|
+ std::optional<NodeDefinition> looked_up_node_def;
|
|
|
|
|
+ if (!prefetched_node_def) {
|
|
|
|
|
+ looked_up_node_def = registry_.getNode(node.type);
|
|
|
|
|
+ }
|
|
|
|
|
+ const std::optional<NodeDefinition>& node_def = prefetched_node_def ? prefetched_node_def : looked_up_node_def;
|
|
|
if (!node_def) {
|
|
if (!node_def) {
|
|
|
result.status = NodeStatus::Failed;
|
|
result.status = NodeStatus::Failed;
|
|
|
result.error = "Node type not found: " + node.type;
|
|
result.error = "Node type not found: " + node.type;
|
|
@@ -2039,10 +2047,19 @@ bool WorkflowEngine::executeLoopBody(
|
|
|
merged_results[key] = value;
|
|
merged_results[key] = value;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Evaluate expressions in body node config before execution
|
|
|
|
|
|
|
+ // Evaluate expressions in body node config before execution. Apply
|
|
|
|
|
+ // config defaults first, same as the main walk does, so a loop
|
|
|
|
|
+ // body node also gets its schema defaults on an already-saved
|
|
|
|
|
+ // workflow rather than only on nodes outside a loop.
|
|
|
WorkflowNode evaluated_body_node = *body_node;
|
|
WorkflowNode evaluated_body_node = *body_node;
|
|
|
t_disabled_reference_error.clear();
|
|
t_disabled_reference_error.clear();
|
|
|
- evaluated_body_node.config = evaluateExpressions(body_node->config, node_input, merged_results, workflow);
|
|
|
|
|
|
|
+ nlohmann::json defaulted_body_config = body_node->config;
|
|
|
|
|
+ auto body_node_def_for_defaults = registry_.getNode(body_node->type);
|
|
|
|
|
+ if (body_node_def_for_defaults) {
|
|
|
|
|
+ defaulted_body_config = smartbotic::common::applyConfigDefaults(
|
|
|
|
|
+ body_node->config, body_node_def_for_defaults->config_schema);
|
|
|
|
|
+ }
|
|
|
|
|
+ evaluated_body_node.config = evaluateExpressions(defaulted_body_config, node_input, merged_results, workflow);
|
|
|
|
|
|
|
|
NodeExecutionResult body_result;
|
|
NodeExecutionResult body_result;
|
|
|
if (!t_disabled_reference_error.empty()) {
|
|
if (!t_disabled_reference_error.empty()) {
|
|
@@ -2054,7 +2071,8 @@ bool WorkflowEngine::executeLoopBody(
|
|
|
body_result.started_at = TimeUtils::nowMs();
|
|
body_result.started_at = TimeUtils::nowMs();
|
|
|
body_result.finished_at = body_result.started_at;
|
|
body_result.finished_at = body_result.started_at;
|
|
|
} else {
|
|
} else {
|
|
|
- body_result = executeNode(evaluated_body_node, node_input, result.execution_id, workflow);
|
|
|
|
|
|
|
+ body_result = executeNode(evaluated_body_node, node_input, result.execution_id, workflow,
|
|
|
|
|
+ body_node_def_for_defaults);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
rejectPauseInLoopBody(body_result);
|
|
rejectPauseInLoopBody(body_result);
|