meta_workflows 0.7.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 +7 -0
- data/CHANGELOG.md +36 -0
- data/README.md +498 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/meta_workflows/controllers/loading_phrases_controller.js +31 -0
- data/app/assets/javascripts/meta_workflows/controllers/redirect_controller.js +15 -0
- data/app/assets/javascripts/meta_workflows/controllers/response_scroll_controller.js +67 -0
- data/app/assets/javascripts/meta_workflows_manifest.js +13 -0
- data/app/assets/stylesheets/meta_workflows/application.css +161 -0
- data/app/controllers/meta_workflows/application_controller.rb +10 -0
- data/app/controllers/meta_workflows/debug_controller.rb +101 -0
- data/app/controllers/meta_workflows/humans_controller.rb +96 -0
- data/app/controllers/meta_workflows/meta_controller.rb +21 -0
- data/app/helpers/meta_workflows/application_helper.rb +7 -0
- data/app/helpers/meta_workflows/debug_helper.rb +54 -0
- data/app/helpers/meta_workflows/execution_helper.rb +77 -0
- data/app/helpers/meta_workflows/formatting_helper.rb +30 -0
- data/app/helpers/meta_workflows/meta_workflows_helper.rb +41 -0
- data/app/helpers/meta_workflows/status_badge_helper.rb +66 -0
- data/app/jobs/meta_workflows/application_job.rb +14 -0
- data/app/jobs/meta_workflows/human_input_job.rb +35 -0
- data/app/jobs/meta_workflows/meta_job.rb +121 -0
- data/app/jobs/meta_workflows/meta_workflow_job.rb +20 -0
- data/app/jobs/meta_workflows/record_redirect_job.rb +36 -0
- data/app/mailers/meta_workflows/application_mailer.rb +8 -0
- data/app/models/meta_workflows/application_record.rb +7 -0
- data/app/models/meta_workflows/chat.rb +20 -0
- data/app/models/meta_workflows/message.rb +11 -0
- data/app/models/meta_workflows/tool_call.rb +7 -0
- data/app/models/meta_workflows/workflow.rb +32 -0
- data/app/models/meta_workflows/workflow_execution.rb +23 -0
- data/app/models/meta_workflows/workflow_step.rb +49 -0
- data/app/models/meta_workflows.rb +7 -0
- data/app/services/meta_workflows/execution_filter_service.rb +80 -0
- data/app/sidekiq/meta_workflows/tools/meta_workflow_tool.rb +86 -0
- data/app/views/layouts/meta_workflows/application.html.erb +17 -0
- data/app/views/layouts/meta_workflows/debug.html.erb +47 -0
- data/app/views/meta_workflows/_loader.html.erb +22 -0
- data/app/views/meta_workflows/_redirect.html.erb +1 -0
- data/app/views/meta_workflows/_response.html.erb +5 -0
- data/app/views/meta_workflows/_response_form.html.erb +36 -0
- data/app/views/meta_workflows/debug/executions.html.erb +187 -0
- data/app/views/meta_workflows/debug/show_execution.html.erb +283 -0
- data/app/views/meta_workflows/debug/show_workflow.html.erb +148 -0
- data/app/views/meta_workflows/debug/workflows.html.erb +110 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20250530220618_create_meta_workflows_workflows.rb +16 -0
- data/db/migrate/20250530220634_create_meta_workflows_workflow_executions.rb +18 -0
- data/db/migrate/20250530220704_create_meta_workflows_chats.rb +16 -0
- data/db/migrate/20250530220722_create_meta_workflows_messages.rb +19 -0
- data/db/migrate/20250530220737_create_meta_workflows_tool_calls.rb +18 -0
- data/db/migrate/20250530220750_create_meta_workflows_workflow_steps.rb +17 -0
- data/db/migrate/20250613213159_add_error_fields_to_workflow_steps.rb +8 -0
- data/lib/meta_workflows/asset_installer.rb +509 -0
- data/lib/meta_workflows/configuration.rb +39 -0
- data/lib/meta_workflows/engine.rb +47 -0
- data/lib/meta_workflows/export_execution_service.rb +56 -0
- data/lib/meta_workflows/version.rb +5 -0
- data/lib/meta_workflows.rb +9 -0
- data/lib/services/meta_workflows/application_service.rb +11 -0
- data/lib/services/meta_workflows/meta_workflow_service.rb +277 -0
- data/lib/services/meta_workflows/updaters/meta_service.rb +39 -0
- data/lib/tasks/meta_workflows_tasks.rake +153 -0
- metadata +219 -0
@@ -0,0 +1,509 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
module MetaWorkflows
|
7
|
+
# Asset installer class to handle file discovery and path resolution
|
8
|
+
class AssetInstaller
|
9
|
+
attr_reader :gem_root, :host_app_root
|
10
|
+
|
11
|
+
# Custom exception for installation errors
|
12
|
+
class InstallationError < StandardError; end
|
13
|
+
class ValidationError < InstallationError; end
|
14
|
+
class PermissionError < InstallationError; end
|
15
|
+
class SourceError < InstallationError; end
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@gem_root = Gem::Specification.find_by_name('meta_workflows').gem_dir
|
19
|
+
@host_app_root = Rails.root
|
20
|
+
@rollback_files = []
|
21
|
+
rescue Gem::LoadError
|
22
|
+
raise InstallationError, 'meta_workflows gem not found. Please ensure it is properly installed.'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Validate environment before installation
|
26
|
+
def valid_environment?
|
27
|
+
puts 'Validating installation environment...'
|
28
|
+
|
29
|
+
errors = validation_errors
|
30
|
+
|
31
|
+
if errors.any?
|
32
|
+
puts 'Validation failed:'
|
33
|
+
errors.each { |error| puts " ✗ #{error}" }
|
34
|
+
return false
|
35
|
+
end
|
36
|
+
|
37
|
+
puts '✓ Environment validation passed'
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Discover all assets that can be installed
|
42
|
+
def discover_assets
|
43
|
+
puts 'Discovering available assets...'
|
44
|
+
|
45
|
+
assets = []
|
46
|
+
|
47
|
+
begin
|
48
|
+
# Discover JavaScript controllers
|
49
|
+
js_assets = discover_javascript_assets
|
50
|
+
assets.concat(js_assets)
|
51
|
+
puts " ✓ Found #{js_assets.count} JavaScript asset(s)" if js_assets.any?
|
52
|
+
|
53
|
+
# Discover stylesheets
|
54
|
+
css_assets = discover_stylesheet_assets
|
55
|
+
assets.concat(css_assets)
|
56
|
+
puts " ✓ Found #{css_assets.count} stylesheet asset(s)" if css_assets.any?
|
57
|
+
|
58
|
+
# Validate discovered assets
|
59
|
+
validate_assets(assets)
|
60
|
+
|
61
|
+
assets
|
62
|
+
rescue StandardError => e
|
63
|
+
raise SourceError, "Failed to discover assets: #{e.message}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Install assets with comprehensive error handling and rollback capability
|
68
|
+
def install_assets(assets)
|
69
|
+
copied_count = 0
|
70
|
+
skipped_count = 0
|
71
|
+
failed_count = 0
|
72
|
+
success = true
|
73
|
+
|
74
|
+
puts 'Installing assets...'
|
75
|
+
|
76
|
+
# Clear rollback tracking
|
77
|
+
@rollback_files.clear
|
78
|
+
|
79
|
+
assets.each do |asset|
|
80
|
+
result = process_single_asset(asset)
|
81
|
+
case result
|
82
|
+
when :copied
|
83
|
+
copied_count += 1
|
84
|
+
puts "✓ Copied #{asset[:relative_path]}"
|
85
|
+
when :skipped
|
86
|
+
skipped_count += 1
|
87
|
+
puts "⚠ Skipped #{asset[:relative_path]} (already exists)"
|
88
|
+
when :failed
|
89
|
+
failed_count += 1
|
90
|
+
success = false
|
91
|
+
puts "✗ Failed to copy #{asset[:relative_path]}"
|
92
|
+
end
|
93
|
+
rescue StandardError => e
|
94
|
+
handle_installation_error(e, asset, failed_count)
|
95
|
+
failed_count += 1
|
96
|
+
success = false
|
97
|
+
end
|
98
|
+
|
99
|
+
{
|
100
|
+
success: success,
|
101
|
+
copied: copied_count,
|
102
|
+
skipped: skipped_count,
|
103
|
+
failed: failed_count
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
# Display comprehensive integration instructions after successful installation
|
108
|
+
def display_integration_instructions(assets, _result)
|
109
|
+
puts '=' * 80
|
110
|
+
puts '🎉 INTEGRATION INSTRUCTIONS'
|
111
|
+
puts '=' * 80
|
112
|
+
puts
|
113
|
+
|
114
|
+
# Analyze what was actually copied
|
115
|
+
copied_controllers = assets.select do |asset|
|
116
|
+
asset[:type] == :javascript && asset[:relative_path].include?('controllers/')
|
117
|
+
end
|
118
|
+
copied_stylesheets = assets.select { |asset| asset[:type] == :stylesheet }
|
119
|
+
|
120
|
+
display_controller_instructions(copied_controllers) if copied_controllers.any?
|
121
|
+
display_stylesheet_instructions(copied_stylesheets) if copied_stylesheets.any?
|
122
|
+
display_general_instructions
|
123
|
+
display_troubleshooting_guide
|
124
|
+
|
125
|
+
puts '=' * 80
|
126
|
+
puts '✨ Happy coding with meta_workflows!'
|
127
|
+
puts '=' * 80
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def validation_errors
|
133
|
+
errors = []
|
134
|
+
|
135
|
+
# Check if we can find the gem
|
136
|
+
errors << "meta_workflows gem directory not found at: #{@gem_root}" unless File.exist?(@gem_root)
|
137
|
+
|
138
|
+
# Check if host application root exists
|
139
|
+
errors << "Host application root not found at: #{@host_app_root}" unless File.exist?(@host_app_root)
|
140
|
+
|
141
|
+
# Check basic write permissions to host app
|
142
|
+
unless writable_directory?(@host_app_root)
|
143
|
+
errors << "No write permission to host application directory: #{@host_app_root}"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Check for required directories in host app
|
147
|
+
required_dirs = [
|
148
|
+
File.join(@host_app_root, 'app'),
|
149
|
+
File.join(@host_app_root, 'app', 'javascript'),
|
150
|
+
File.join(@host_app_root, 'app', 'assets')
|
151
|
+
]
|
152
|
+
|
153
|
+
required_dirs.each do |dir|
|
154
|
+
errors << "Required directory missing: #{dir}" unless File.exist?(dir)
|
155
|
+
end
|
156
|
+
|
157
|
+
errors
|
158
|
+
end
|
159
|
+
|
160
|
+
def process_single_asset(asset)
|
161
|
+
install_single_asset(asset)
|
162
|
+
rescue StandardError
|
163
|
+
:failed
|
164
|
+
end
|
165
|
+
|
166
|
+
def handle_installation_error(error, asset, _failed_count)
|
167
|
+
puts "✗ Failed to copy #{asset[:relative_path]}: #{error.message}"
|
168
|
+
|
169
|
+
# If we have critical failures, consider rollback
|
170
|
+
return unless should_rollback?(error)
|
171
|
+
|
172
|
+
puts 'Critical error encountered. Rolling back changes...'
|
173
|
+
rollback_installation
|
174
|
+
raise InstallationError, "Installation failed and was rolled back: #{error.message}"
|
175
|
+
end
|
176
|
+
|
177
|
+
# Display instructions for Stimulus controllers
|
178
|
+
def display_controller_instructions(controllers)
|
179
|
+
puts '📋 STIMULUS CONTROLLERS'
|
180
|
+
puts '-' * 40
|
181
|
+
puts
|
182
|
+
puts 'The following Stimulus controllers have been installed:'
|
183
|
+
puts
|
184
|
+
|
185
|
+
controllers.each do |controller|
|
186
|
+
controller_name = File.basename(controller[:relative_path], '.js').gsub('_controller', '').tr('_', '-')
|
187
|
+
puts "• #{controller_name.upcase}_CONTROLLER"
|
188
|
+
puts " Location: #{controller[:relative_path]}"
|
189
|
+
puts " Usage: data-controller=\"meta-workflows--#{controller_name}\""
|
190
|
+
puts
|
191
|
+
end
|
192
|
+
|
193
|
+
puts '🔧 STIMULUS SETUP REQUIRED:'
|
194
|
+
puts
|
195
|
+
puts 'Add the following line to your app/javascript/controllers/index.js file:'
|
196
|
+
puts
|
197
|
+
puts ' import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"'
|
198
|
+
puts ' eagerLoadControllersFrom("controllers/meta_workflows", application)'
|
199
|
+
puts
|
200
|
+
puts 'This will automatically load and register all meta_workflows controllers with the'
|
201
|
+
puts '"meta-workflows--" prefix (e.g., "meta-workflows--redirect").'
|
202
|
+
puts
|
203
|
+
|
204
|
+
display_controller_usage_examples
|
205
|
+
end
|
206
|
+
|
207
|
+
# Display usage examples for each controller
|
208
|
+
def display_controller_usage_examples
|
209
|
+
puts '📖 CONTROLLER USAGE EXAMPLES:'
|
210
|
+
puts
|
211
|
+
puts '• REDIRECT CONTROLLER:'
|
212
|
+
puts ' <div data-controller="meta-workflows--redirect"'
|
213
|
+
puts ' data-meta-workflows--redirect-url-value="/success"'
|
214
|
+
puts ' data-meta-workflows--redirect-delay-value="2000">'
|
215
|
+
puts ' Redirecting...'
|
216
|
+
puts ' </div>'
|
217
|
+
puts
|
218
|
+
puts '• RESPONSE SCROLL CONTROLLER:'
|
219
|
+
puts ' <div data-controller="meta-workflows--response-scroll"'
|
220
|
+
puts ' id="response-content-container">'
|
221
|
+
puts ' <!-- Auto-scrolls to bottom as content changes -->'
|
222
|
+
puts ' </div>'
|
223
|
+
puts
|
224
|
+
puts '• LOADING PHRASES CONTROLLER:'
|
225
|
+
puts ' <div data-controller="meta-workflows--loading-phrases"'
|
226
|
+
loading_phrases_example_line
|
227
|
+
puts ' <span data-meta-workflows--loading-phrases-target="message">Loading...</span>'
|
228
|
+
puts ' </div>'
|
229
|
+
puts
|
230
|
+
end
|
231
|
+
|
232
|
+
def loading_phrases_example_line
|
233
|
+
puts ' data-meta-workflows--loading-phrases-phrases-value=' \
|
234
|
+
'\'["Processing...", "Almost done...", "Finishing up..."]\'>'
|
235
|
+
end
|
236
|
+
|
237
|
+
# Display instructions for stylesheets
|
238
|
+
def display_stylesheet_instructions(stylesheets)
|
239
|
+
puts '🎨 STYLESHEETS'
|
240
|
+
puts '-' * 40
|
241
|
+
puts
|
242
|
+
puts 'The following stylesheets have been installed:'
|
243
|
+
puts
|
244
|
+
|
245
|
+
stylesheets.each do |stylesheet|
|
246
|
+
puts "• #{stylesheet[:relative_path]}"
|
247
|
+
end
|
248
|
+
puts
|
249
|
+
|
250
|
+
puts '🔧 STYLESHEET SETUP REQUIRED:'
|
251
|
+
puts
|
252
|
+
puts '1. Add to your application.css file:'
|
253
|
+
puts
|
254
|
+
puts ' /*'
|
255
|
+
puts ' *= require meta_workflows/application'
|
256
|
+
puts ' */'
|
257
|
+
puts
|
258
|
+
puts '2. Or with Sass/SCSS, add to application.scss:'
|
259
|
+
puts
|
260
|
+
puts ' @import "meta_workflows/application";'
|
261
|
+
puts
|
262
|
+
puts '3. With CSS imports, add to your main CSS file:'
|
263
|
+
puts
|
264
|
+
puts ' @import url("./meta_workflows/application.css");'
|
265
|
+
puts
|
266
|
+
end
|
267
|
+
|
268
|
+
# Display general integration instructions
|
269
|
+
def display_general_instructions
|
270
|
+
puts '⚙️ GENERAL INTEGRATION NOTES'
|
271
|
+
puts '-' * 40
|
272
|
+
puts
|
273
|
+
puts '• Ensure your Rails application has Hotwire/Stimulus properly configured'
|
274
|
+
puts '• Controllers use the "meta-workflows--" prefix to avoid naming conflicts'
|
275
|
+
puts '• All assets maintain the meta_workflows namespace structure'
|
276
|
+
puts '• Assets are copied to standard Rails asset directories for easy management'
|
277
|
+
puts '• You can customize the copied files as needed for your application'
|
278
|
+
puts
|
279
|
+
puts '📁 ASSET LOCATIONS:'
|
280
|
+
puts ' • JavaScript Controllers: app/javascript/controllers/meta_workflows/'
|
281
|
+
puts ' • Stylesheets: app/assets/stylesheets/meta_workflows/'
|
282
|
+
puts
|
283
|
+
end
|
284
|
+
|
285
|
+
# Display troubleshooting guide
|
286
|
+
def display_troubleshooting_guide
|
287
|
+
puts '🔍 TROUBLESHOOTING'
|
288
|
+
puts '-' * 40
|
289
|
+
puts
|
290
|
+
puts 'If controllers are not working:'
|
291
|
+
puts ' 1. Check that Stimulus is properly installed and configured'
|
292
|
+
puts ' 2. Verify controllers are registered in your application.js'
|
293
|
+
puts ' 3. Check browser console for JavaScript errors'
|
294
|
+
puts ' 4. Ensure data-controller attributes use correct naming: "meta-workflows--controller-name"'
|
295
|
+
puts
|
296
|
+
puts 'If stylesheets are not loading:'
|
297
|
+
puts ' 1. Verify CSS imports in your application.css or application.scss'
|
298
|
+
puts ' 2. Check that Rails asset pipeline is properly configured'
|
299
|
+
puts ' 3. Run "rails assets:precompile" in production'
|
300
|
+
puts
|
301
|
+
puts 'For additional help:'
|
302
|
+
puts ' • Check the meta_workflows gem documentation'
|
303
|
+
puts ' • Review Rails asset pipeline documentation'
|
304
|
+
puts ' • Verify Hotwire/Stimulus setup in your application'
|
305
|
+
puts
|
306
|
+
end
|
307
|
+
|
308
|
+
# Install a single asset with comprehensive error handling
|
309
|
+
def install_single_asset(asset)
|
310
|
+
validate_source_file(asset)
|
311
|
+
destination_dir = File.dirname(asset[:destination_path])
|
312
|
+
create_destination_directory(destination_dir)
|
313
|
+
check_destination_permissions(destination_dir)
|
314
|
+
|
315
|
+
# Check if file already exists
|
316
|
+
return :skipped if File.exist?(asset[:destination_path]) && !should_overwrite?(asset[:relative_path])
|
317
|
+
|
318
|
+
copy_asset_file(asset)
|
319
|
+
end
|
320
|
+
|
321
|
+
def validate_source_file(asset)
|
322
|
+
# Validate source file exists
|
323
|
+
raise SourceError, "Source file not found: #{asset[:source_path]}" unless File.exist?(asset[:source_path])
|
324
|
+
|
325
|
+
# Validate source file is readable
|
326
|
+
return if File.readable?(asset[:source_path])
|
327
|
+
|
328
|
+
raise PermissionError, "Cannot read source file: #{asset[:source_path]}"
|
329
|
+
end
|
330
|
+
|
331
|
+
def create_destination_directory(destination_dir)
|
332
|
+
FileUtils.mkdir_p(destination_dir)
|
333
|
+
rescue Errno::EACCES
|
334
|
+
raise PermissionError, "Cannot create destination directory: #{destination_dir}"
|
335
|
+
rescue Errno::ENOSPC
|
336
|
+
raise InstallationError, "No space left on device when creating: #{destination_dir}"
|
337
|
+
end
|
338
|
+
|
339
|
+
def check_destination_permissions(destination_dir)
|
340
|
+
return if writable_directory?(destination_dir)
|
341
|
+
|
342
|
+
raise PermissionError, "No write permission to destination directory: #{destination_dir}"
|
343
|
+
end
|
344
|
+
|
345
|
+
def copy_asset_file(asset)
|
346
|
+
FileUtils.cp(asset[:source_path], asset[:destination_path])
|
347
|
+
verify_file_copy(asset)
|
348
|
+
@rollback_files << asset[:destination_path]
|
349
|
+
:copied
|
350
|
+
rescue Errno::EACCES
|
351
|
+
raise PermissionError, "Permission denied when copying to: #{asset[:destination_path]}"
|
352
|
+
rescue Errno::ENOSPC
|
353
|
+
raise InstallationError, "No space left on device when copying to: #{asset[:destination_path]}"
|
354
|
+
rescue Errno::EIO
|
355
|
+
raise InstallationError, "I/O error when copying to: #{asset[:destination_path]}"
|
356
|
+
end
|
357
|
+
|
358
|
+
def verify_file_copy(asset)
|
359
|
+
# Verify the copy was successful
|
360
|
+
unless File.exist?(asset[:destination_path])
|
361
|
+
raise InstallationError, 'File copy appeared successful but destination file not found'
|
362
|
+
end
|
363
|
+
|
364
|
+
# Verify file sizes match
|
365
|
+
return if File.size(asset[:source_path]) == File.size(asset[:destination_path])
|
366
|
+
|
367
|
+
raise InstallationError, 'File copy incomplete - size mismatch'
|
368
|
+
end
|
369
|
+
|
370
|
+
# Check if directory is writable
|
371
|
+
def writable_directory?(path)
|
372
|
+
File.writable?(path) && File.executable?(path)
|
373
|
+
end
|
374
|
+
|
375
|
+
# Validate discovered assets
|
376
|
+
def validate_assets(assets)
|
377
|
+
errors = []
|
378
|
+
|
379
|
+
assets.each do |asset|
|
380
|
+
errors << "Source file missing: #{asset[:source_path]}" unless File.exist?(asset[:source_path])
|
381
|
+
|
382
|
+
errors << "Source file not readable: #{asset[:source_path]}" unless File.readable?(asset[:source_path])
|
383
|
+
|
384
|
+
destination_dir = File.dirname(asset[:destination_path])
|
385
|
+
if File.exist?(destination_dir) && !writable_directory?(destination_dir)
|
386
|
+
errors << "Cannot write to destination directory: #{destination_dir}"
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
return unless errors.any?
|
391
|
+
|
392
|
+
error_message = "Asset validation failed:\n#{errors.map { |e| " - #{e}" }.join("\n")}"
|
393
|
+
raise ValidationError, error_message
|
394
|
+
end
|
395
|
+
|
396
|
+
# Prompt user for overwrite confirmation
|
397
|
+
def should_overwrite?(relative_path)
|
398
|
+
print "File #{relative_path} already exists. Overwrite? (y/N): "
|
399
|
+
response = $stdin.gets.chomp
|
400
|
+
response.downcase.start_with?('y')
|
401
|
+
end
|
402
|
+
|
403
|
+
# Determine if we should rollback based on the error type
|
404
|
+
def should_rollback?(error)
|
405
|
+
# Rollback on critical errors that could leave system in inconsistent state
|
406
|
+
error.is_a?(PermissionError) ||
|
407
|
+
(error.is_a?(InstallationError) && error.message.include?('No space left'))
|
408
|
+
end
|
409
|
+
|
410
|
+
# Rollback installation by removing copied files
|
411
|
+
def rollback_installation
|
412
|
+
return if @rollback_files.empty?
|
413
|
+
|
414
|
+
puts "Rolling back #{@rollback_files.count} file(s)..."
|
415
|
+
|
416
|
+
@rollback_files.reverse_each do |file_path|
|
417
|
+
if File.exist?(file_path)
|
418
|
+
File.delete(file_path)
|
419
|
+
puts " ✓ Removed #{file_path}"
|
420
|
+
end
|
421
|
+
rescue StandardError => e
|
422
|
+
puts " ✗ Could not remove #{file_path}: #{e.message}"
|
423
|
+
end
|
424
|
+
|
425
|
+
@rollback_files.clear
|
426
|
+
end
|
427
|
+
|
428
|
+
# Discover JavaScript controllers in the meta_workflows namespace
|
429
|
+
def discover_javascript_assets
|
430
|
+
assets = []
|
431
|
+
js_source_dir = File.join(gem_root, 'app', 'assets', 'javascripts', 'meta_workflows')
|
432
|
+
|
433
|
+
return assets unless Dir.exist?(js_source_dir)
|
434
|
+
|
435
|
+
begin
|
436
|
+
Dir.glob(File.join(js_source_dir, '**', '*.js')).each do |source_path|
|
437
|
+
assets << build_javascript_asset(source_path, js_source_dir)
|
438
|
+
end
|
439
|
+
rescue StandardError => e
|
440
|
+
raise SourceError, "Error discovering JavaScript assets: #{e.message}"
|
441
|
+
end
|
442
|
+
|
443
|
+
assets
|
444
|
+
end
|
445
|
+
|
446
|
+
def build_javascript_asset(source_path, js_source_dir)
|
447
|
+
relative_path = source_path.sub("#{js_source_dir}/", '')
|
448
|
+
|
449
|
+
# Determine destination based on file type
|
450
|
+
if relative_path.start_with?('controllers/')
|
451
|
+
build_controller_asset(source_path, relative_path)
|
452
|
+
else
|
453
|
+
build_general_js_asset(source_path, relative_path)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def build_controller_asset(source_path, relative_path)
|
458
|
+
controller_name = File.basename(relative_path)
|
459
|
+
destination_path = File.join(host_app_root, 'app', 'javascript', 'controllers', 'meta_workflows',
|
460
|
+
controller_name)
|
461
|
+
display_path = "app/javascript/controllers/meta_workflows/#{controller_name}"
|
462
|
+
|
463
|
+
{
|
464
|
+
type: :javascript,
|
465
|
+
source_path: source_path,
|
466
|
+
destination_path: destination_path,
|
467
|
+
relative_path: display_path
|
468
|
+
}
|
469
|
+
end
|
470
|
+
|
471
|
+
def build_general_js_asset(source_path, relative_path)
|
472
|
+
destination_path = File.join(host_app_root, 'app', 'javascript', 'meta_workflows', relative_path)
|
473
|
+
display_path = "app/javascript/meta_workflows/#{relative_path}"
|
474
|
+
|
475
|
+
{
|
476
|
+
type: :javascript,
|
477
|
+
source_path: source_path,
|
478
|
+
destination_path: destination_path,
|
479
|
+
relative_path: display_path
|
480
|
+
}
|
481
|
+
end
|
482
|
+
|
483
|
+
# Discover stylesheets in the meta_workflows namespace
|
484
|
+
def discover_stylesheet_assets
|
485
|
+
assets = []
|
486
|
+
css_source_dir = File.join(gem_root, 'app', 'assets', 'stylesheets', 'meta_workflows')
|
487
|
+
|
488
|
+
return assets unless Dir.exist?(css_source_dir)
|
489
|
+
|
490
|
+
begin
|
491
|
+
Dir.glob(File.join(css_source_dir, '**', '*.css')).each do |source_path|
|
492
|
+
relative_path = source_path.sub("#{css_source_dir}/", '')
|
493
|
+
destination_path = File.join(host_app_root, 'app', 'assets', 'stylesheets', 'meta_workflows', relative_path)
|
494
|
+
|
495
|
+
assets << {
|
496
|
+
type: :stylesheet,
|
497
|
+
source_path: source_path,
|
498
|
+
destination_path: destination_path,
|
499
|
+
relative_path: "app/assets/stylesheets/meta_workflows/#{relative_path}"
|
500
|
+
}
|
501
|
+
end
|
502
|
+
rescue StandardError => e
|
503
|
+
raise SourceError, "Error discovering stylesheet assets: #{e.message}"
|
504
|
+
end
|
505
|
+
|
506
|
+
assets
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :default_workflow_timeout,
|
6
|
+
:max_retry_attempts,
|
7
|
+
:workflow_definitions_path,
|
8
|
+
:enable_ui_components,
|
9
|
+
:sidekiq_queue_name
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@default_workflow_timeout = 1800 # 30 minutes in seconds
|
13
|
+
@max_retry_attempts = 3
|
14
|
+
@workflow_definitions_path = if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
15
|
+
Rails.root.join(
|
16
|
+
'config/workflows'
|
17
|
+
)
|
18
|
+
end
|
19
|
+
@enable_ui_components = true
|
20
|
+
@sidekiq_queue_name = 'meta_workflows'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
attr_writer :configuration
|
26
|
+
|
27
|
+
def configuration
|
28
|
+
@configuration ||= Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure
|
32
|
+
yield(configuration)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset_configuration!
|
36
|
+
@configuration = Configuration.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
isolate_namespace MetaWorkflows
|
6
|
+
|
7
|
+
# Add autoload paths for engine components
|
8
|
+
config.autoload_paths += %W[
|
9
|
+
#{root}/app/controllers/concerns
|
10
|
+
#{root}/app/models/concerns
|
11
|
+
#{root}/app/services
|
12
|
+
#{root}/app/components
|
13
|
+
#{root}/app/jobs
|
14
|
+
#{root}/app/sidekiq
|
15
|
+
#{root}/lib
|
16
|
+
]
|
17
|
+
|
18
|
+
# Configure generators
|
19
|
+
config.generators do |g|
|
20
|
+
g.test_framework :rspec
|
21
|
+
g.fixture_replacement :factory_bot
|
22
|
+
g.factory_bot dir: 'spec/factories'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Initialize engine configuration
|
26
|
+
initializer 'meta_workflows.configure' do |app|
|
27
|
+
# Ensure Sidekiq is available for background jobs
|
28
|
+
if defined?(Sidekiq)
|
29
|
+
# Configure Sidekiq for MetaWorkflows jobs
|
30
|
+
app.config.active_job.queue_adapter = :sidekiq
|
31
|
+
end
|
32
|
+
|
33
|
+
# Configure ViewComponent if available
|
34
|
+
if defined?(ViewComponent)
|
35
|
+
existing_paths = [*ViewComponent::Base.view_component_path]
|
36
|
+
engine_component_path = root.join('app', 'components').to_s
|
37
|
+
|
38
|
+
app.config.view_component.view_component_path = (existing_paths + [engine_component_path]).uniq
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Configure routes
|
43
|
+
initializer 'meta_workflows.routes' do |app|
|
44
|
+
# Routes will be mounted by the host application
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MetaWorkflows
|
4
|
+
class ExportExecutionService
|
5
|
+
def initialize(execution)
|
6
|
+
@execution = execution
|
7
|
+
end
|
8
|
+
|
9
|
+
def export_data
|
10
|
+
{
|
11
|
+
execution: execution_data,
|
12
|
+
workflow: workflow_data,
|
13
|
+
workflow_steps: workflow_steps_data
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def execution_data
|
20
|
+
{
|
21
|
+
id: @execution.id,
|
22
|
+
workflow_id: @execution.workflow_id,
|
23
|
+
workflow_name: @execution.workflow.name,
|
24
|
+
record_type: @execution.record_type,
|
25
|
+
record_id: @execution.record_id,
|
26
|
+
current_step: @execution.current_step,
|
27
|
+
completed: @execution.completed,
|
28
|
+
workflow_params: @execution.workflow_params,
|
29
|
+
created_at: @execution.created_at,
|
30
|
+
updated_at: @execution.updated_at
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def workflow_data
|
35
|
+
{
|
36
|
+
id: @execution.workflow.id,
|
37
|
+
name: @execution.workflow.name,
|
38
|
+
recipe: @execution.workflow.recipe
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def workflow_steps_data
|
43
|
+
@execution.workflow_steps.order(:step).map do |step|
|
44
|
+
{
|
45
|
+
id: step.id,
|
46
|
+
step: step.step,
|
47
|
+
workflow_execution_id: step.workflow_execution_id,
|
48
|
+
chat_id: step.chat_id,
|
49
|
+
created_at: step.created_at,
|
50
|
+
updated_at: step.updated_at,
|
51
|
+
chat: step.chat&.as_json(include: { messages: { include: :tool_calls } })
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|