roast-ai 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +20 -0
- data/CLAUDE.md +52 -1
- data/Gemfile +3 -1
- data/Gemfile.lock +63 -16
- data/README.md +90 -5
- data/bin/roast +1 -1
- data/claude-swarm.yml +210 -0
- data/docs/AGENT_STEPS.md +264 -0
- data/examples/agent_workflow/README.md +75 -0
- data/examples/agent_workflow/apply_refactorings/prompt.md +22 -0
- data/examples/agent_workflow/identify_code_smells/prompt.md +15 -0
- data/examples/agent_workflow/summarize_improvements/prompt.md +18 -0
- data/examples/agent_workflow/workflow.yml +16 -0
- data/examples/available_tools_demo/README.md +42 -0
- data/examples/available_tools_demo/analyze_files/prompt.md +6 -0
- data/examples/available_tools_demo/explore_directory/prompt.md +6 -0
- data/examples/available_tools_demo/workflow.yml +32 -0
- data/examples/available_tools_demo/write_summary/prompt.md +6 -0
- data/examples/case_when/detect_language/prompt.md +2 -2
- data/examples/grading/run_coverage.rb +0 -2
- data/examples/iteration/analyze_complexity/prompt.md +2 -2
- data/examples/iteration/generate_recommendations/prompt.md +2 -2
- data/examples/iteration/implement_fix/prompt.md +2 -2
- data/examples/iteration/prioritize_issues/prompt.md +1 -1
- data/examples/iteration/prompts/analyze_file.md +2 -2
- data/examples/iteration/prompts/generate_summary.md +1 -1
- data/examples/iteration/prompts/update_report.md +3 -3
- data/examples/iteration/prompts/write_report.md +3 -3
- data/examples/iteration/read_file/prompt.md +2 -2
- data/examples/iteration/select_next_issue/prompt.md +2 -2
- data/examples/iteration/update_fix_count/prompt.md +4 -4
- data/examples/iteration/verify_fix/prompt.md +3 -3
- data/examples/mcp/README.md +3 -3
- data/examples/mcp/analyze_changes/prompt.md +1 -1
- data/examples/mcp/database_workflow.yml +1 -1
- data/examples/mcp/fetch_pr_context/prompt.md +1 -1
- data/examples/mcp/github_workflow.yml +1 -1
- data/examples/mcp/post_review/prompt.md +1 -1
- data/examples/pre_post_processing/analyze_test_file/prompt.md +1 -1
- data/examples/pre_post_processing/improve_test_coverage/prompt.md +1 -1
- data/examples/pre_post_processing/optimize_test_performance/prompt.md +1 -1
- data/examples/pre_post_processing/post_processing/aggregate_metrics/prompt.md +2 -2
- data/examples/pre_post_processing/post_processing/generate_summary_report/prompt.md +1 -1
- data/examples/pre_post_processing/pre_processing/setup_test_environment/prompt.md +1 -1
- data/examples/pre_post_processing/validate_changes/prompt.md +2 -2
- data/examples/user_input/README.md +90 -0
- data/examples/user_input/funny_name/create_backstory/prompt.md +10 -0
- data/examples/user_input/funny_name/workflow.yml +26 -0
- data/examples/user_input/generate_summary/prompt.md +11 -0
- data/examples/user_input/simple_input_demo/workflow.yml +35 -0
- data/examples/user_input/survey_workflow.yml +71 -0
- data/examples/user_input/welcome_message/prompt.md +3 -0
- data/examples/user_input/workflow.yml +73 -0
- data/examples/workflow_generator/create_workflow_files/prompt.md +1 -1
- data/lib/roast/errors.rb +6 -4
- data/lib/roast/helpers/function_caching_interceptor.rb +0 -2
- data/lib/roast/helpers/logger.rb +12 -35
- data/lib/roast/helpers/minitest_coverage_runner.rb +0 -1
- data/lib/roast/helpers/prompt_loader.rb +0 -2
- data/lib/roast/resources/api_resource.rb +0 -4
- data/lib/roast/resources/url_resource.rb +0 -3
- data/lib/roast/resources.rb +0 -8
- data/lib/roast/tools/ask_user.rb +0 -2
- data/lib/roast/tools/bash.rb +0 -3
- data/lib/roast/tools/cmd.rb +0 -3
- data/lib/roast/tools/coding_agent.rb +1 -8
- data/lib/roast/tools/grep.rb +0 -3
- data/lib/roast/tools/helpers/coding_agent_message_formatter.rb +1 -4
- data/lib/roast/tools/read_file.rb +0 -2
- data/lib/roast/tools/search_file.rb +0 -2
- data/lib/roast/tools/update_files.rb +0 -4
- data/lib/roast/tools/write_file.rb +0 -3
- data/lib/roast/tools.rb +0 -13
- data/lib/roast/value_objects/step_name.rb +14 -3
- data/lib/roast/value_objects/workflow_path.rb +0 -2
- data/lib/roast/value_objects.rb +4 -4
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/agent_step.rb +26 -0
- data/lib/roast/workflow/api_configuration.rb +0 -4
- data/lib/roast/workflow/base_iteration_step.rb +0 -4
- data/lib/roast/workflow/base_step.rb +54 -28
- data/lib/roast/workflow/base_workflow.rb +2 -21
- data/lib/roast/workflow/case_executor.rb +0 -1
- data/lib/roast/workflow/case_step.rb +0 -4
- data/lib/roast/workflow/command_executor.rb +0 -2
- data/lib/roast/workflow/conditional_executor.rb +0 -1
- data/lib/roast/workflow/conditional_step.rb +0 -4
- data/lib/roast/workflow/configuration.rb +3 -66
- data/lib/roast/workflow/configuration_loader.rb +0 -2
- data/lib/roast/workflow/configuration_parser.rb +1 -7
- data/lib/roast/workflow/dot_access_hash.rb +16 -1
- data/lib/roast/workflow/error_handler.rb +0 -3
- data/lib/roast/workflow/expression_evaluator.rb +0 -3
- data/lib/roast/workflow/file_state_repository.rb +0 -5
- data/lib/roast/workflow/input_executor.rb +41 -0
- data/lib/roast/workflow/input_step.rb +163 -0
- data/lib/roast/workflow/iteration_executor.rb +0 -2
- data/lib/roast/workflow/output_handler.rb +0 -2
- data/lib/roast/workflow/output_manager.rb +0 -2
- data/lib/roast/workflow/replay_handler.rb +0 -3
- data/lib/roast/workflow/resource_resolver.rb +0 -3
- data/lib/roast/workflow/session_manager.rb +0 -3
- data/lib/roast/workflow/state_manager.rb +0 -2
- data/lib/roast/workflow/step_executor_coordinator.rb +34 -11
- data/lib/roast/workflow/step_executor_factory.rb +0 -5
- data/lib/roast/workflow/step_executor_registry.rb +1 -4
- data/lib/roast/workflow/step_executors/hash_step_executor.rb +0 -3
- data/lib/roast/workflow/step_executors/parallel_step_executor.rb +0 -3
- data/lib/roast/workflow/step_executors/string_step_executor.rb +0 -2
- data/lib/roast/workflow/step_factory.rb +56 -0
- data/lib/roast/workflow/step_loader.rb +30 -16
- data/lib/roast/workflow/step_orchestrator.rb +3 -2
- data/lib/roast/workflow/step_type_resolver.rb +28 -1
- data/lib/roast/workflow/validator.rb +0 -4
- data/lib/roast/workflow/workflow_executor.rb +0 -16
- data/lib/roast/workflow/workflow_initializer.rb +1 -8
- data/lib/roast/workflow/workflow_runner.rb +0 -7
- data/lib/roast/workflow.rb +0 -15
- data/lib/roast.rb +55 -10
- data/roast.gemspec +2 -1
- data/schema/workflow.json +46 -0
- metadata +43 -6
- data/lib/roast/helpers.rb +0 -12
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roast
|
4
|
+
module Workflow
|
5
|
+
# Handles execution of input steps
|
6
|
+
class InputExecutor
|
7
|
+
def initialize(workflow, context_path, state_manager, workflow_executor = nil)
|
8
|
+
@workflow = workflow
|
9
|
+
@context_path = context_path
|
10
|
+
@state_manager = state_manager
|
11
|
+
@workflow_executor = workflow_executor
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute_input(input_config)
|
15
|
+
# Interpolate the prompt if workflow executor is available
|
16
|
+
if @workflow_executor && input_config["prompt"]
|
17
|
+
interpolated_config = input_config.dup
|
18
|
+
interpolated_config["prompt"] = @workflow_executor.interpolate(input_config["prompt"])
|
19
|
+
else
|
20
|
+
interpolated_config = input_config
|
21
|
+
end
|
22
|
+
|
23
|
+
# Create and execute an InputStep
|
24
|
+
input_step = InputStep.new(
|
25
|
+
@workflow,
|
26
|
+
config: interpolated_config,
|
27
|
+
name: input_config["name"] || "input_#{Time.now.to_i}",
|
28
|
+
context_path: @context_path,
|
29
|
+
)
|
30
|
+
|
31
|
+
result = input_step.call
|
32
|
+
|
33
|
+
# Store in 'previous' for conditional checks
|
34
|
+
@workflow.output["previous"] = result
|
35
|
+
@state_manager.save_state("previous", result)
|
36
|
+
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "timeout"
|
4
|
+
|
5
|
+
module Roast
|
6
|
+
module Workflow
|
7
|
+
class InputStep < BaseStep
|
8
|
+
attr_reader :prompt_text, :type, :required, :default, :timeout, :options, :step_name
|
9
|
+
|
10
|
+
def initialize(workflow, config:, **kwargs)
|
11
|
+
super(workflow, **kwargs)
|
12
|
+
parse_config(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
# Get user input based on the configured type
|
17
|
+
result = case type
|
18
|
+
when "boolean"
|
19
|
+
prompt_boolean
|
20
|
+
when "choice"
|
21
|
+
prompt_choice
|
22
|
+
when "password"
|
23
|
+
prompt_password
|
24
|
+
else
|
25
|
+
prompt_text_input
|
26
|
+
end
|
27
|
+
|
28
|
+
# Store the result in workflow state if a name was provided
|
29
|
+
store_in_state(result) if step_name
|
30
|
+
|
31
|
+
result
|
32
|
+
rescue Timeout::Error
|
33
|
+
handle_timeout
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def parse_config(config)
|
39
|
+
@prompt_text = config["prompt"] || raise_config_error("Missing 'prompt' in input configuration")
|
40
|
+
@step_name = config["name"]
|
41
|
+
@type = config["type"] || "text"
|
42
|
+
@required = config.fetch("required", false)
|
43
|
+
@default = config["default"]
|
44
|
+
@timeout = config["timeout"]
|
45
|
+
@options = config["options"]
|
46
|
+
|
47
|
+
validate_config
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate_config
|
51
|
+
if type == "choice" && options.nil?
|
52
|
+
raise_config_error("Missing 'options' for choice type input")
|
53
|
+
end
|
54
|
+
|
55
|
+
if type == "boolean" && default && ![true, false, "true", "false", "yes", "no"].include?(default)
|
56
|
+
raise_config_error("Invalid default value for boolean type: #{default}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def prompt_text_input
|
61
|
+
loop do
|
62
|
+
result = if timeout
|
63
|
+
with_timeout { ::CLI::UI.ask(prompt_text, default: default) }
|
64
|
+
else
|
65
|
+
::CLI::UI.ask(prompt_text, default: default)
|
66
|
+
end
|
67
|
+
|
68
|
+
if required && result.to_s.strip.empty?
|
69
|
+
puts ::CLI::UI.fmt("{{red:This field is required. Please provide a value.}}")
|
70
|
+
next
|
71
|
+
end
|
72
|
+
|
73
|
+
return result
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def prompt_boolean
|
78
|
+
if timeout
|
79
|
+
with_timeout { ::CLI::UI.confirm(prompt_text, default: boolean_default) }
|
80
|
+
else
|
81
|
+
::CLI::UI.confirm(prompt_text, default: boolean_default)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def prompt_choice
|
86
|
+
if timeout
|
87
|
+
with_timeout { ::CLI::UI.ask(prompt_text, options: options, default: default) }
|
88
|
+
else
|
89
|
+
::CLI::UI.ask(prompt_text, options: options, default: default)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def prompt_password
|
94
|
+
require "io/console"
|
95
|
+
|
96
|
+
loop do
|
97
|
+
result = if timeout
|
98
|
+
with_timeout { prompt_password_with_echo_off }
|
99
|
+
else
|
100
|
+
prompt_password_with_echo_off
|
101
|
+
end
|
102
|
+
|
103
|
+
if required && result.to_s.strip.empty?
|
104
|
+
puts ::CLI::UI.fmt("{{red:This field is required. Please provide a value.}}")
|
105
|
+
next
|
106
|
+
end
|
107
|
+
|
108
|
+
return result
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def prompt_password_with_echo_off
|
113
|
+
::CLI::UI.with_frame_color(:blue) do
|
114
|
+
print("🔒 #{prompt_text} ")
|
115
|
+
|
116
|
+
password = if $stdin.tty?
|
117
|
+
# Use noecho for TTY environments
|
118
|
+
$stdin.noecho { $stdin.gets }.chomp
|
119
|
+
else
|
120
|
+
# Fall back to regular input for non-TTY environments
|
121
|
+
warn("[WARNING] Password will be visible (not running in TTY)")
|
122
|
+
$stdin.gets.chomp
|
123
|
+
end
|
124
|
+
|
125
|
+
puts # Add newline after password input
|
126
|
+
password
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def boolean_default
|
131
|
+
case default
|
132
|
+
when true, "true", "yes"
|
133
|
+
true
|
134
|
+
when false, "false", "no"
|
135
|
+
false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def with_timeout(&block)
|
140
|
+
Timeout.timeout(timeout, &block)
|
141
|
+
end
|
142
|
+
|
143
|
+
def handle_timeout
|
144
|
+
puts ::CLI::UI.fmt("{{yellow:Input timed out after #{timeout} seconds}}")
|
145
|
+
|
146
|
+
if default
|
147
|
+
puts ::CLI::UI.fmt("{{yellow:Using default value: #{default}}}")
|
148
|
+
default
|
149
|
+
elsif required
|
150
|
+
raise_config_error("Required input timed out with no default value")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def store_in_state(value)
|
155
|
+
workflow.output[step_name] = value
|
156
|
+
end
|
157
|
+
|
158
|
+
def raise_config_error(message)
|
159
|
+
raise WorkflowExecutor::ConfigurationError, message
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -24,7 +24,6 @@ module Roast
|
|
24
24
|
raise WorkflowExecutor::ConfigurationError, "Missing 'until' condition in repeat configuration" unless until_condition
|
25
25
|
|
26
26
|
# Create and execute a RepeatStep
|
27
|
-
require "roast/workflow/repeat_step" unless defined?(RepeatStep)
|
28
27
|
repeat_step = RepeatStep.new(
|
29
28
|
@workflow,
|
30
29
|
steps: steps,
|
@@ -64,7 +63,6 @@ module Roast
|
|
64
63
|
raise WorkflowExecutor::ConfigurationError, "Missing 'steps' in each configuration" unless steps
|
65
64
|
|
66
65
|
# Create and execute an EachStep
|
67
|
-
require "roast/workflow/each_step" unless defined?(EachStep)
|
68
66
|
each_step = EachStep.new(
|
69
67
|
@workflow,
|
70
68
|
collection_expr: collection_expr,
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/workflow/case_executor"
|
4
|
-
require "roast/workflow/conditional_executor"
|
5
|
-
require "roast/workflow/step_executor_factory"
|
6
|
-
require "roast/workflow/step_type_resolver"
|
7
|
-
|
8
3
|
module Roast
|
9
4
|
module Workflow
|
10
5
|
# Coordinates the execution of different types of steps
|
@@ -29,20 +24,21 @@ module Roast
|
|
29
24
|
|
30
25
|
# Execute a list of steps
|
31
26
|
def execute_steps(workflow_steps)
|
32
|
-
workflow_steps.
|
27
|
+
workflow_steps.each_with_index do |step, index|
|
28
|
+
is_last_step = (index == workflow_steps.length - 1)
|
33
29
|
case step
|
34
30
|
when Hash
|
35
|
-
execute(step)
|
31
|
+
execute(step, is_last_step: is_last_step)
|
36
32
|
when Array
|
37
|
-
execute(step)
|
33
|
+
execute(step, is_last_step: is_last_step)
|
38
34
|
when String
|
39
|
-
execute(step)
|
35
|
+
execute(step, is_last_step: is_last_step)
|
40
36
|
# Handle pause after string steps
|
41
37
|
if @context.workflow.pause_step_name == step
|
42
38
|
Kernel.binding.irb # rubocop:disable Lint/Debugger
|
43
39
|
end
|
44
40
|
else
|
45
|
-
step_orchestrator.execute_step(step)
|
41
|
+
step_orchestrator.execute_step(step, is_last_step: is_last_step)
|
46
42
|
end
|
47
43
|
end
|
48
44
|
end
|
@@ -63,6 +59,8 @@ module Roast
|
|
63
59
|
when StepTypeResolver::COMMAND_STEP
|
64
60
|
# Command steps should also go through interpolation
|
65
61
|
execute_string_step(step, options)
|
62
|
+
when StepTypeResolver::AGENT_STEP
|
63
|
+
execute_agent_step(step, options)
|
66
64
|
when StepTypeResolver::GLOB_STEP
|
67
65
|
execute_glob_step(step)
|
68
66
|
when StepTypeResolver::ITERATION_STEP
|
@@ -71,6 +69,8 @@ module Roast
|
|
71
69
|
execute_conditional_step(step)
|
72
70
|
when StepTypeResolver::CASE_STEP
|
73
71
|
execute_case_step(step)
|
72
|
+
when StepTypeResolver::INPUT_STEP
|
73
|
+
execute_input_step(step)
|
74
74
|
when StepTypeResolver::HASH_STEP
|
75
75
|
execute_hash_step(step)
|
76
76
|
when StepTypeResolver::PARALLEL_STEP
|
@@ -117,6 +117,15 @@ module Roast
|
|
117
117
|
)
|
118
118
|
end
|
119
119
|
|
120
|
+
def input_executor
|
121
|
+
@input_executor ||= dependencies[:input_executor] || InputExecutor.new(
|
122
|
+
context.workflow,
|
123
|
+
context.context_path,
|
124
|
+
dependencies[:state_manager] || dependencies[:workflow_executor].state_manager,
|
125
|
+
workflow_executor,
|
126
|
+
)
|
127
|
+
end
|
128
|
+
|
120
129
|
def step_orchestrator
|
121
130
|
dependencies[:step_orchestrator]
|
122
131
|
end
|
@@ -171,6 +180,15 @@ module Roast
|
|
171
180
|
end
|
172
181
|
end
|
173
182
|
|
183
|
+
def execute_agent_step(step, options = {})
|
184
|
+
# Extract the step name without the ^ prefix
|
185
|
+
step_name = StepTypeResolver.extract_name(step)
|
186
|
+
|
187
|
+
# Load and execute the agent step
|
188
|
+
exit_on_error = options.fetch(:exit_on_error, context.exit_on_error?(step))
|
189
|
+
step_orchestrator.execute_step(step_name, exit_on_error:, step_key: options[:step_key], agent_type: :coding_agent)
|
190
|
+
end
|
191
|
+
|
174
192
|
def execute_glob_step(step)
|
175
193
|
Dir.glob(step).join("\n")
|
176
194
|
end
|
@@ -196,6 +214,10 @@ module Roast
|
|
196
214
|
case_executor.execute_case(step)
|
197
215
|
end
|
198
216
|
|
217
|
+
def execute_input_step(step)
|
218
|
+
input_executor.execute_input(step["input"])
|
219
|
+
end
|
220
|
+
|
199
221
|
def execute_hash_step(step)
|
200
222
|
name, command = step.to_a.flatten
|
201
223
|
interpolated_name = interpolator.interpolate(name)
|
@@ -235,7 +257,8 @@ module Roast
|
|
235
257
|
def execute_standard_step(step, options)
|
236
258
|
exit_on_error = options.fetch(:exit_on_error, true)
|
237
259
|
step_key = options[:step_key]
|
238
|
-
|
260
|
+
is_last_step = options[:is_last_step]
|
261
|
+
step_orchestrator.execute_step(step, exit_on_error:, step_key:, is_last_step:)
|
239
262
|
end
|
240
263
|
|
241
264
|
def validate_each_step!(step)
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/workflow/step_executor_registry"
|
4
|
-
require "roast/workflow/step_executors/hash_step_executor"
|
5
|
-
require "roast/workflow/step_executors/parallel_step_executor"
|
6
|
-
require "roast/workflow/step_executors/string_step_executor"
|
7
|
-
|
8
3
|
module Roast
|
9
4
|
module Workflow
|
10
5
|
# Factory for creating step executors - now delegates to registry
|
@@ -43,10 +43,7 @@ module Roast
|
|
43
43
|
def clear!
|
44
44
|
@executors.clear
|
45
45
|
@type_matchers.clear
|
46
|
-
|
47
|
-
if defined?(StepExecutorFactory)
|
48
|
-
StepExecutorFactory.instance_variable_set(:@defaults_registered, false)
|
49
|
-
end
|
46
|
+
StepExecutorFactory.instance_variable_set(:@defaults_registered, false)
|
50
47
|
end
|
51
48
|
|
52
49
|
# Get all registered executors (useful for debugging)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roast
|
4
|
+
module Workflow
|
5
|
+
# Factory for creating step instances based on step characteristics
|
6
|
+
class StepFactory
|
7
|
+
class << self
|
8
|
+
# Create a step instance based on the step type and characteristics
|
9
|
+
#
|
10
|
+
# @param workflow [BaseWorkflow] The workflow instance
|
11
|
+
# @param step_name [String, StepName] The name of the step
|
12
|
+
# @param options [Hash] Additional options for step creation
|
13
|
+
# @return [BaseStep] The appropriate step instance
|
14
|
+
def create(workflow, step_name, options = {})
|
15
|
+
name = normalize_step_name(step_name)
|
16
|
+
|
17
|
+
# Determine the step class based on characteristics
|
18
|
+
step_class = determine_step_class(name, options)
|
19
|
+
|
20
|
+
# Create the step instance with appropriate parameters
|
21
|
+
build_step_instance(step_class, workflow, name, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def normalize_step_name(step_name)
|
27
|
+
step_name.is_a?(Roast::ValueObjects::StepName) ? step_name : Roast::ValueObjects::StepName.new(step_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def determine_step_class(name, options)
|
31
|
+
# Check if this is an agent step (indicated by special processing needs)
|
32
|
+
if options[:agent_type] == :coding_agent
|
33
|
+
Roast::Workflow::AgentStep
|
34
|
+
elsif name.plain_text?
|
35
|
+
# Plain text steps are always prompt steps
|
36
|
+
options[:agent_type] == :coding_agent ? Roast::Workflow::AgentStep : Roast::Workflow::PromptStep
|
37
|
+
else
|
38
|
+
# Default to BaseStep for directory-based steps
|
39
|
+
Roast::Workflow::BaseStep
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_step_instance(step_class, workflow, name, options)
|
44
|
+
step_params = {
|
45
|
+
name:,
|
46
|
+
}
|
47
|
+
|
48
|
+
# Add context path if provided
|
49
|
+
step_params[:context_path] = options[:context_path] if options[:context_path]
|
50
|
+
|
51
|
+
step_class.new(workflow, **step_params)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "roast/value_objects/step_name"
|
4
|
-
require "roast/workflow/workflow_context"
|
5
|
-
require "roast/workflow/base_step"
|
6
|
-
require "roast/workflow/prompt_step"
|
7
|
-
|
8
3
|
module Roast
|
9
4
|
module Workflow
|
10
5
|
# Handles loading and instantiation of workflow steps
|
@@ -47,8 +42,9 @@ module Roast
|
|
47
42
|
#
|
48
43
|
# @param step_name [String, StepName] The name of the step to load
|
49
44
|
# @param step_key [String] The configuration key for the step (optional)
|
45
|
+
# @param options [Hash] Additional options for step loading
|
50
46
|
# @return [BaseStep] The loaded step instance
|
51
|
-
def load(step_name, step_key: nil)
|
47
|
+
def load(step_name, step_key: nil, is_last_step: nil, **options)
|
52
48
|
name = step_name.is_a?(Roast::ValueObjects::StepName) ? step_name : Roast::ValueObjects::StepName.new(step_name)
|
53
49
|
|
54
50
|
# Get step config for per-step path
|
@@ -57,17 +53,17 @@ module Roast
|
|
57
53
|
|
58
54
|
# First check for a prompt step (contains spaces)
|
59
55
|
if name.plain_text?
|
60
|
-
step =
|
56
|
+
step = StepFactory.create(workflow, name, options)
|
61
57
|
# Use step_key for configuration if provided, otherwise use name
|
62
58
|
config_key = step_key || name.to_s
|
63
|
-
configure_step(step, config_key)
|
59
|
+
configure_step(step, config_key, is_last_step:)
|
64
60
|
return step
|
65
61
|
end
|
66
62
|
|
67
63
|
# Look for Ruby file in various locations
|
68
64
|
step_file_path = find_step_file(name.to_s, per_step_path)
|
69
65
|
if step_file_path
|
70
|
-
return load_ruby_step(step_file_path, name.to_s)
|
66
|
+
return load_ruby_step(step_file_path, name.to_s, is_last_step:)
|
71
67
|
end
|
72
68
|
|
73
69
|
# Look for step directory
|
@@ -76,7 +72,10 @@ module Roast
|
|
76
72
|
raise StepNotFoundError.new("Step directory or file not found: #{name}", step_name: name.to_s)
|
77
73
|
end
|
78
74
|
|
79
|
-
|
75
|
+
# Use factory to create the appropriate step instance
|
76
|
+
step = StepFactory.create(workflow, name, options.merge(context_path: step_directory))
|
77
|
+
configure_step(step, name.to_s, is_last_step:)
|
78
|
+
step
|
80
79
|
end
|
81
80
|
|
82
81
|
private
|
@@ -141,7 +140,7 @@ module Roast
|
|
141
140
|
end
|
142
141
|
|
143
142
|
# Load a Ruby step from a file
|
144
|
-
def load_ruby_step(file_path, step_name)
|
143
|
+
def load_ruby_step(file_path, step_name, is_last_step: nil)
|
145
144
|
$stderr.puts "Requiring step file: #{file_path}"
|
146
145
|
|
147
146
|
begin
|
@@ -154,18 +153,24 @@ module Roast
|
|
154
153
|
|
155
154
|
step_class = step_name.classify.constantize
|
156
155
|
context = File.dirname(file_path)
|
157
|
-
|
156
|
+
# For Ruby steps, we instantiate the specific class directly
|
157
|
+
# Convert step_name to StepName value object
|
158
|
+
step_name_obj = Roast::ValueObjects::StepName.new(step_name)
|
159
|
+
step = step_class.new(workflow, name: step_name_obj, context_path: context)
|
160
|
+
configure_step(step, step_name, is_last_step:)
|
161
|
+
step
|
158
162
|
end
|
159
163
|
|
160
164
|
# Create and configure a step instance
|
161
|
-
def create_step_instance(step_class, step_name, context_path)
|
162
|
-
|
163
|
-
|
165
|
+
def create_step_instance(step_class, step_name, context_path, options = {})
|
166
|
+
is_last_step = options[:is_last_step]
|
167
|
+
step = StepFactory.create(workflow, step_name, options.merge(context_path: context_path))
|
168
|
+
configure_step(step, step_name, is_last_step:)
|
164
169
|
step
|
165
170
|
end
|
166
171
|
|
167
172
|
# Configure a step instance with settings from config_hash
|
168
|
-
def configure_step(step, step_name)
|
173
|
+
def configure_step(step, step_name, is_last_step: nil)
|
169
174
|
step_config = config_hash[step_name]
|
170
175
|
|
171
176
|
# Always set the model
|
@@ -176,6 +181,11 @@ module Roast
|
|
176
181
|
|
177
182
|
# Apply additional configuration if present
|
178
183
|
apply_step_configuration(step, step_config) if step_config.present?
|
184
|
+
|
185
|
+
# Set print_response to true for the last step if not already configured
|
186
|
+
if is_last_step && !step_config&.key?("print_response")
|
187
|
+
step.print_response = true
|
188
|
+
end
|
179
189
|
end
|
180
190
|
|
181
191
|
# Determine which model to use for the step
|
@@ -189,6 +199,10 @@ module Roast
|
|
189
199
|
step.json = step_config["json"] if step_config.key?("json")
|
190
200
|
step.params = step_config["params"] if step_config.key?("params")
|
191
201
|
step.coerce_to = step_config["coerce_to"].to_sym if step_config.key?("coerce_to")
|
202
|
+
|
203
|
+
if step_config.key?("available_tools")
|
204
|
+
step.available_tools = step_config["available_tools"]
|
205
|
+
end
|
192
206
|
end
|
193
207
|
end
|
194
208
|
end
|
@@ -22,7 +22,7 @@ module Roast
|
|
22
22
|
@workflow_executor = workflow_executor
|
23
23
|
end
|
24
24
|
|
25
|
-
def execute_step(name, exit_on_error: true, step_key: nil)
|
25
|
+
def execute_step(name, exit_on_error: true, step_key: nil, **options)
|
26
26
|
resource_type = @workflow.respond_to?(:resource) ? @workflow.resource&.type : nil
|
27
27
|
|
28
28
|
@error_handler.with_error_handling(name, resource_type: resource_type) do
|
@@ -30,7 +30,8 @@ module Roast
|
|
30
30
|
|
31
31
|
# Use step_key for loading if provided, otherwise use name
|
32
32
|
load_key = step_key || name
|
33
|
-
|
33
|
+
is_last_step = options[:is_last_step]
|
34
|
+
step_object = @step_loader.load(name, step_key: load_key, is_last_step:, **options)
|
34
35
|
step_result = step_object.call
|
35
36
|
|
36
37
|
# Store result in workflow output
|