ruby_llm-agents 1.3.3 → 2.0.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/README.md +101 -334
- data/app/controllers/concerns/ruby_llm/agents/sortable.rb +0 -1
- data/app/controllers/ruby_llm/agents/agents_controller.rb +5 -56
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +22 -106
- data/app/controllers/ruby_llm/agents/executions_controller.rb +4 -114
- data/app/controllers/ruby_llm/agents/tenants_controller.rb +30 -2
- data/app/helpers/ruby_llm/agents/application_helper.rb +19 -53
- data/app/models/ruby_llm/agents/execution/analytics.rb +13 -54
- data/app/models/ruby_llm/agents/execution/scopes.rb +61 -14
- data/app/models/ruby_llm/agents/execution.rb +46 -10
- data/app/models/ruby_llm/agents/execution_detail.rb +18 -0
- data/app/models/ruby_llm/agents/tenant/budgetable.rb +132 -24
- data/app/models/ruby_llm/agents/tenant/incrementable.rb +117 -0
- data/app/models/ruby_llm/agents/tenant/resettable.rb +128 -0
- data/app/models/ruby_llm/agents/tenant/trackable.rb +46 -12
- data/app/models/ruby_llm/agents/tenant.rb +2 -3
- data/app/models/ruby_llm/agents/tenant_budget.rb +6 -3
- data/app/services/ruby_llm/agents/agent_registry.rb +6 -112
- data/app/views/layouts/ruby_llm/agents/application.html.erb +87 -252
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +71 -218
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +20 -63
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +44 -131
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +16 -57
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +39 -104
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +29 -82
- data/app/views/ruby_llm/agents/agents/_empty_state.html.erb +4 -14
- data/app/views/ruby_llm/agents/agents/index.html.erb +105 -274
- data/app/views/ruby_llm/agents/agents/show.html.erb +248 -378
- data/app/views/ruby_llm/agents/dashboard/_action_center.html.erb +29 -52
- data/app/views/ruby_llm/agents/dashboard/_tenant_budget.html.erb +73 -99
- data/app/views/ruby_llm/agents/dashboard/index.html.erb +228 -433
- data/app/views/ruby_llm/agents/executions/_execution.html.erb +1 -1
- data/app/views/ruby_llm/agents/executions/_filters.html.erb +4 -25
- data/app/views/ruby_llm/agents/executions/_list.html.erb +111 -152
- data/app/views/ruby_llm/agents/executions/index.html.erb +5 -7
- data/app/views/ruby_llm/agents/executions/show.html.erb +528 -989
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +5 -21
- data/app/views/ruby_llm/agents/shared/_executions_table.html.erb +70 -191
- data/app/views/ruby_llm/agents/shared/_filter_dropdown.html.erb +16 -44
- data/app/views/ruby_llm/agents/shared/_select_dropdown.html.erb +12 -41
- data/app/views/ruby_llm/agents/shared/_status_badge.html.erb +11 -65
- data/app/views/ruby_llm/agents/shared/_tenant_filter.html.erb +6 -5
- data/app/views/ruby_llm/agents/system_config/show.html.erb +240 -351
- data/app/views/ruby_llm/agents/tenants/_form.html.erb +67 -77
- data/app/views/ruby_llm/agents/tenants/edit.html.erb +7 -9
- data/app/views/ruby_llm/agents/tenants/index.html.erb +100 -122
- data/app/views/ruby_llm/agents/tenants/show.html.erb +146 -336
- data/config/routes.rb +0 -13
- data/lib/generators/ruby_llm_agents/install_generator.rb +9 -14
- data/lib/generators/ruby_llm_agents/migrate_structure_generator.rb +2 -12
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +0 -2
- data/lib/generators/ruby_llm_agents/templates/add_usage_counters_to_tenants_migration.rb.tt +37 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +1 -2
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +1 -1
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/create_execution_details_migration.rb.tt +27 -0
- data/lib/generators/ruby_llm_agents/templates/create_tenants_migration.rb.tt +25 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +9 -12
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +40 -71
- data/lib/generators/ruby_llm_agents/templates/remove_agent_version_migration.rb.tt +13 -0
- data/lib/generators/ruby_llm_agents/templates/remove_workflow_columns_migration.rb.tt +19 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +2 -4
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +0 -1
- data/lib/generators/ruby_llm_agents/templates/split_execution_details_migration.rb.tt +232 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +58 -262
- data/lib/ruby_llm/agents/audio/speaker.rb +0 -1
- data/lib/ruby_llm/agents/audio/transcriber.rb +0 -1
- data/lib/ruby_llm/agents/base_agent.rb +52 -6
- data/lib/ruby_llm/agents/core/base/callbacks.rb +142 -0
- data/lib/ruby_llm/agents/core/base.rb +23 -55
- data/lib/ruby_llm/agents/core/configuration.rb +58 -117
- data/lib/ruby_llm/agents/core/errors.rb +0 -58
- data/lib/ruby_llm/agents/core/instrumentation.rb +157 -110
- data/lib/ruby_llm/agents/core/llm_tenant.rb +8 -7
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +157 -17
- data/lib/ruby_llm/agents/dsl/caching.rb +33 -2
- data/lib/ruby_llm/agents/dsl/reliability.rb +148 -0
- data/lib/ruby_llm/agents/dsl.rb +1 -2
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +1 -13
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +2 -2
- data/lib/ruby_llm/agents/image/editor/dsl.rb +0 -14
- data/lib/ruby_llm/agents/image/editor/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/editor.rb +0 -1
- data/lib/ruby_llm/agents/image/generator.rb +0 -21
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +0 -1
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +0 -13
- data/lib/ruby_llm/agents/image/transformer/execution.rb +1 -10
- data/lib/ruby_llm/agents/image/transformer.rb +0 -1
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +1 -2
- data/lib/ruby_llm/agents/image/variator/execution.rb +1 -2
- data/lib/ruby_llm/agents/infrastructure/alert_manager.rb +78 -173
- data/lib/ruby_llm/agents/infrastructure/attempt_tracker.rb +1 -0
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +66 -2
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +0 -12
- data/lib/ruby_llm/agents/infrastructure/circuit_breaker.rb +10 -13
- data/lib/ruby_llm/agents/infrastructure/reliability.rb +37 -2
- data/lib/ruby_llm/agents/pipeline/context.rb +0 -1
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +28 -4
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +3 -10
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +88 -55
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +5 -41
- data/lib/ruby_llm/agents/rails/engine.rb +6 -6
- data/lib/ruby_llm/agents/results/base.rb +1 -49
- data/lib/ruby_llm/agents/text/embedder.rb +0 -1
- data/lib/ruby_llm/agents.rb +1 -9
- data/lib/tasks/ruby_llm_agents.rake +34 -0
- metadata +12 -81
- data/app/controllers/ruby_llm/agents/api_configurations_controller.rb +0 -214
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +0 -544
- data/app/mailers/ruby_llm/agents/alert_mailer.rb +0 -84
- data/app/mailers/ruby_llm/agents/application_mailer.rb +0 -28
- data/app/models/ruby_llm/agents/api_configuration.rb +0 -386
- data/app/models/ruby_llm/agents/execution/workflow.rb +0 -170
- data/app/models/ruby_llm/agents/tenant/configurable.rb +0 -135
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +0 -98
- data/app/views/ruby_llm/agents/agents/_version_comparison.html.erb +0 -186
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +0 -126
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.html.erb +0 -107
- data/app/views/ruby_llm/agents/alert_mailer/alert_notification.text.erb +0 -18
- data/app/views/ruby_llm/agents/api_configurations/_api_key_field.html.erb +0 -34
- data/app/views/ruby_llm/agents/api_configurations/_form.html.erb +0 -288
- data/app/views/ruby_llm/agents/api_configurations/edit.html.erb +0 -95
- data/app/views/ruby_llm/agents/api_configurations/edit_tenant.html.erb +0 -97
- data/app/views/ruby_llm/agents/api_configurations/show.html.erb +0 -214
- data/app/views/ruby_llm/agents/api_configurations/tenant.html.erb +0 -179
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +0 -73
- data/app/views/ruby_llm/agents/dashboard/_alerts_feed.html.erb +0 -62
- data/app/views/ruby_llm/agents/dashboard/_breaker_strip.html.erb +0 -47
- data/app/views/ruby_llm/agents/dashboard/_budgets_bar.html.erb +0 -75
- data/app/views/ruby_llm/agents/dashboard/_model_comparison.html.erb +0 -56
- data/app/views/ruby_llm/agents/dashboard/_model_cost_breakdown.html.erb +0 -115
- data/app/views/ruby_llm/agents/dashboard/_now_strip.html.erb +0 -59
- data/app/views/ruby_llm/agents/dashboard/_top_errors.html.erb +0 -60
- data/app/views/ruby_llm/agents/executions/_workflow_summary.html.erb +0 -86
- data/app/views/ruby_llm/agents/executions/dry_run.html.erb +0 -149
- data/app/views/ruby_llm/agents/shared/_breadcrumbs.html.erb +0 -48
- data/app/views/ruby_llm/agents/shared/_nav_link.html.erb +0 -27
- data/app/views/ruby_llm/agents/shared/_stat_card.html.erb +0 -14
- data/app/views/ruby_llm/agents/shared/_workflow_type_badge.html.erb +0 -35
- data/app/views/ruby_llm/agents/workflows/_empty_state.html.erb +0 -22
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +0 -228
- data/app/views/ruby_llm/agents/workflows/_structure_dsl.html.erb +0 -539
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +0 -76
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +0 -74
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +0 -108
- data/app/views/ruby_llm/agents/workflows/_workflow_diagram.html.erb +0 -920
- data/app/views/ruby_llm/agents/workflows/index.html.erb +0 -179
- data/app/views/ruby_llm/agents/workflows/show.html.erb +0 -467
- data/lib/generators/ruby_llm_agents/api_configuration_generator.rb +0 -100
- data/lib/generators/ruby_llm_agents/templates/add_workflow_migration.rb.tt +0 -38
- data/lib/generators/ruby_llm_agents/templates/application_workflow.rb.tt +0 -48
- data/lib/generators/ruby_llm_agents/templates/create_api_configurations_migration.rb.tt +0 -90
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +0 -551
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +0 -181
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +0 -274
- data/lib/ruby_llm/agents/core/resolved_config.rb +0 -348
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +0 -95
- data/lib/ruby_llm/agents/infrastructure/redactor.rb +0 -130
- data/lib/ruby_llm/agents/results/moderation_result.rb +0 -158
- data/lib/ruby_llm/agents/text/moderator.rb +0 -237
- data/lib/ruby_llm/agents/workflow/approval.rb +0 -205
- data/lib/ruby_llm/agents/workflow/approval_store.rb +0 -179
- data/lib/ruby_llm/agents/workflow/async.rb +0 -220
- data/lib/ruby_llm/agents/workflow/async_executor.rb +0 -156
- data/lib/ruby_llm/agents/workflow/dsl/executor.rb +0 -467
- data/lib/ruby_llm/agents/workflow/dsl/input_schema.rb +0 -244
- data/lib/ruby_llm/agents/workflow/dsl/iteration_executor.rb +0 -289
- data/lib/ruby_llm/agents/workflow/dsl/parallel_group.rb +0 -107
- data/lib/ruby_llm/agents/workflow/dsl/route_builder.rb +0 -150
- data/lib/ruby_llm/agents/workflow/dsl/schedule_helpers.rb +0 -187
- data/lib/ruby_llm/agents/workflow/dsl/step_config.rb +0 -352
- data/lib/ruby_llm/agents/workflow/dsl/step_executor.rb +0 -415
- data/lib/ruby_llm/agents/workflow/dsl/wait_config.rb +0 -257
- data/lib/ruby_llm/agents/workflow/dsl/wait_executor.rb +0 -317
- data/lib/ruby_llm/agents/workflow/dsl.rb +0 -576
- data/lib/ruby_llm/agents/workflow/instrumentation.rb +0 -249
- data/lib/ruby_llm/agents/workflow/notifiers/base.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/email.rb +0 -117
- data/lib/ruby_llm/agents/workflow/notifiers/slack.rb +0 -180
- data/lib/ruby_llm/agents/workflow/notifiers/webhook.rb +0 -121
- data/lib/ruby_llm/agents/workflow/notifiers.rb +0 -70
- data/lib/ruby_llm/agents/workflow/orchestrator.rb +0 -416
- data/lib/ruby_llm/agents/workflow/result.rb +0 -592
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +0 -185
- data/lib/ruby_llm/agents/workflow/throttle_manager.rb +0 -206
- data/lib/ruby_llm/agents/workflow/wait_result.rb +0 -213
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RubyLLM
|
|
4
|
-
module Agents
|
|
5
|
-
class Workflow
|
|
6
|
-
module DSL
|
|
7
|
-
# Executes wait steps within a workflow
|
|
8
|
-
#
|
|
9
|
-
# Handles the four types of waits:
|
|
10
|
-
# - delay: Simple time-based pause
|
|
11
|
-
# - until: Poll until a condition is met
|
|
12
|
-
# - schedule: Wait until a specific time
|
|
13
|
-
# - approval: Wait for human approval
|
|
14
|
-
#
|
|
15
|
-
# @example Executing a delay wait
|
|
16
|
-
# executor = WaitExecutor.new(wait_config, workflow)
|
|
17
|
-
# result = executor.execute
|
|
18
|
-
#
|
|
19
|
-
# @api private
|
|
20
|
-
class WaitExecutor
|
|
21
|
-
# @param config [WaitConfig] The wait configuration
|
|
22
|
-
# @param workflow [Workflow] The workflow instance
|
|
23
|
-
# @param approval_store [ApprovalStore, nil] Custom approval store
|
|
24
|
-
def initialize(config, workflow, approval_store: nil)
|
|
25
|
-
@config = config
|
|
26
|
-
@workflow = workflow
|
|
27
|
-
@approval_store = approval_store || ApprovalStore.store
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Execute the wait step
|
|
31
|
-
#
|
|
32
|
-
# @return [WaitResult] The result of the wait
|
|
33
|
-
def execute
|
|
34
|
-
# Check conditions first
|
|
35
|
-
unless @config.should_execute?(@workflow)
|
|
36
|
-
return WaitResult.skipped(@config.type, reason: "Condition not met")
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
case @config.type
|
|
40
|
-
when :delay
|
|
41
|
-
execute_delay
|
|
42
|
-
when :until
|
|
43
|
-
execute_until
|
|
44
|
-
when :schedule
|
|
45
|
-
execute_schedule
|
|
46
|
-
when :approval
|
|
47
|
-
execute_approval
|
|
48
|
-
else
|
|
49
|
-
raise ArgumentError, "Unknown wait type: #{@config.type}"
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
private
|
|
54
|
-
|
|
55
|
-
# Execute a simple delay
|
|
56
|
-
#
|
|
57
|
-
# @return [WaitResult]
|
|
58
|
-
def execute_delay
|
|
59
|
-
duration = resolve_duration(@config.duration)
|
|
60
|
-
sleep_with_interruption(duration)
|
|
61
|
-
WaitResult.success(:delay, duration)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Execute a conditional wait (polling)
|
|
65
|
-
#
|
|
66
|
-
# @return [WaitResult]
|
|
67
|
-
def execute_until
|
|
68
|
-
started_at = Time.now
|
|
69
|
-
interval = normalize_duration(@config.poll_interval)
|
|
70
|
-
timeout = @config.timeout ? normalize_duration(@config.timeout) : nil
|
|
71
|
-
max_interval = @config.max_interval ? normalize_duration(@config.max_interval) : nil
|
|
72
|
-
|
|
73
|
-
loop do
|
|
74
|
-
# Check condition
|
|
75
|
-
if evaluate_condition(@config.condition)
|
|
76
|
-
waited = Time.now - started_at
|
|
77
|
-
return WaitResult.success(:until, waited)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Check timeout
|
|
81
|
-
if timeout
|
|
82
|
-
elapsed = Time.now - started_at
|
|
83
|
-
if elapsed >= timeout
|
|
84
|
-
return handle_timeout(:until, elapsed)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Wait before next poll
|
|
89
|
-
sleep_with_interruption(interval)
|
|
90
|
-
|
|
91
|
-
# Apply exponential backoff if configured
|
|
92
|
-
if @config.exponential_backoff?
|
|
93
|
-
interval = apply_backoff(interval, max_interval)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Execute a scheduled wait
|
|
99
|
-
#
|
|
100
|
-
# @return [WaitResult]
|
|
101
|
-
def execute_schedule
|
|
102
|
-
target_time = evaluate_time(@config.condition)
|
|
103
|
-
|
|
104
|
-
unless target_time.is_a?(Time)
|
|
105
|
-
raise ArgumentError, "Schedule condition must return a Time, got #{target_time.class}"
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
wait_duration = target_time - Time.now
|
|
109
|
-
|
|
110
|
-
if wait_duration > 0
|
|
111
|
-
sleep_with_interruption(wait_duration)
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
WaitResult.success(:schedule, [wait_duration, 0].max, target_time: target_time)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Execute a human approval wait
|
|
118
|
-
#
|
|
119
|
-
# @return [WaitResult]
|
|
120
|
-
def execute_approval
|
|
121
|
-
started_at = Time.now
|
|
122
|
-
|
|
123
|
-
# Create approval request
|
|
124
|
-
approval = create_approval_request
|
|
125
|
-
|
|
126
|
-
# Save to store
|
|
127
|
-
@approval_store.save(approval)
|
|
128
|
-
|
|
129
|
-
# Send notifications
|
|
130
|
-
send_notifications(approval)
|
|
131
|
-
|
|
132
|
-
# Set up reminder tracking
|
|
133
|
-
reminder_sent = false
|
|
134
|
-
reminder_after = @config.reminder_after ? normalize_duration(@config.reminder_after) : nil
|
|
135
|
-
reminder_interval = @config.reminder_interval ? normalize_duration(@config.reminder_interval) : nil
|
|
136
|
-
|
|
137
|
-
# Poll for approval or timeout
|
|
138
|
-
timeout = @config.timeout ? normalize_duration(@config.timeout) : nil
|
|
139
|
-
poll_interval = normalize_duration(@config.poll_interval)
|
|
140
|
-
|
|
141
|
-
loop do
|
|
142
|
-
# Refresh approval from store
|
|
143
|
-
approval = @approval_store.find(approval.id)
|
|
144
|
-
|
|
145
|
-
unless approval
|
|
146
|
-
waited = Time.now - started_at
|
|
147
|
-
return WaitResult.timeout(:approval, waited, :fail,
|
|
148
|
-
error: "Approval not found")
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# Check if approved
|
|
152
|
-
if approval.approved?
|
|
153
|
-
waited = Time.now - started_at
|
|
154
|
-
return WaitResult.approved(approval.id, approval.approved_by, waited)
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Check if rejected
|
|
158
|
-
if approval.rejected?
|
|
159
|
-
waited = Time.now - started_at
|
|
160
|
-
return WaitResult.rejected(approval.id, approval.rejected_by, waited,
|
|
161
|
-
reason: approval.reason)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# Check if expired
|
|
165
|
-
if approval.expired? || approval.timed_out?
|
|
166
|
-
waited = Time.now - started_at
|
|
167
|
-
return handle_timeout(:approval, waited, approval_id: approval.id)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# Check timeout
|
|
171
|
-
if timeout
|
|
172
|
-
elapsed = Time.now - started_at
|
|
173
|
-
if elapsed >= timeout
|
|
174
|
-
approval.expire!
|
|
175
|
-
@approval_store.save(approval)
|
|
176
|
-
return handle_timeout(:approval, elapsed, approval_id: approval.id)
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Check if reminder should be sent
|
|
181
|
-
if reminder_after && approval.should_remind?(reminder_after,
|
|
182
|
-
reminder_interval: reminder_interval)
|
|
183
|
-
send_reminder(approval)
|
|
184
|
-
approval.mark_reminded!
|
|
185
|
-
@approval_store.save(approval)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Wait before next poll
|
|
189
|
-
sleep_with_interruption(poll_interval)
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def create_approval_request
|
|
194
|
-
Approval.new(
|
|
195
|
-
workflow_id: @workflow.object_id.to_s,
|
|
196
|
-
workflow_type: @workflow.class.name,
|
|
197
|
-
name: @config.name,
|
|
198
|
-
approvers: @config.approvers,
|
|
199
|
-
expires_at: @config.timeout ? Time.now + normalize_duration(@config.timeout) : nil,
|
|
200
|
-
metadata: {
|
|
201
|
-
workflow_input: @workflow.input.to_h,
|
|
202
|
-
created_by: "workflow"
|
|
203
|
-
}
|
|
204
|
-
)
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def send_notifications(approval)
|
|
208
|
-
return if @config.notify_channels.empty?
|
|
209
|
-
|
|
210
|
-
message = resolve_message(@config.message, approval)
|
|
211
|
-
Notifiers.notify(approval, message, channels: @config.notify_channels)
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
def send_reminder(approval)
|
|
215
|
-
return if @config.notify_channels.empty?
|
|
216
|
-
|
|
217
|
-
message = resolve_message(@config.message, approval)
|
|
218
|
-
@config.notify_channels.each do |channel|
|
|
219
|
-
notifier = Notifiers[channel]
|
|
220
|
-
notifier&.remind(approval, message)
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def resolve_message(message_config, approval)
|
|
225
|
-
case message_config
|
|
226
|
-
when String
|
|
227
|
-
message_config
|
|
228
|
-
when Proc
|
|
229
|
-
@workflow.instance_exec(approval, &message_config)
|
|
230
|
-
else
|
|
231
|
-
"Approval required: #{approval.name}"
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
def handle_timeout(type, elapsed, **metadata)
|
|
236
|
-
action = @config.on_timeout
|
|
237
|
-
|
|
238
|
-
case action
|
|
239
|
-
when :continue
|
|
240
|
-
WaitResult.timeout(type, elapsed, :continue, **metadata)
|
|
241
|
-
when :skip_next
|
|
242
|
-
WaitResult.timeout(type, elapsed, :skip_next, **metadata)
|
|
243
|
-
when :escalate
|
|
244
|
-
handle_escalation(type, elapsed, metadata)
|
|
245
|
-
else # :fail
|
|
246
|
-
WaitResult.timeout(type, elapsed, :fail, **metadata)
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def handle_escalation(type, elapsed, metadata)
|
|
251
|
-
if @config.escalate_to
|
|
252
|
-
# Create escalated approval or notify escalation target
|
|
253
|
-
WaitResult.timeout(type, elapsed, :escalate,
|
|
254
|
-
escalated_to: @config.escalate_to,
|
|
255
|
-
**metadata)
|
|
256
|
-
else
|
|
257
|
-
WaitResult.timeout(type, elapsed, :fail, **metadata)
|
|
258
|
-
end
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
def resolve_duration(duration)
|
|
262
|
-
normalize_duration(duration)
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def normalize_duration(duration)
|
|
266
|
-
if duration.respond_to?(:to_f)
|
|
267
|
-
duration.to_f
|
|
268
|
-
else
|
|
269
|
-
duration.to_i.to_f
|
|
270
|
-
end
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def evaluate_condition(condition)
|
|
274
|
-
case condition
|
|
275
|
-
when Proc
|
|
276
|
-
@workflow.instance_exec(&condition)
|
|
277
|
-
when Symbol
|
|
278
|
-
@workflow.send(condition)
|
|
279
|
-
else
|
|
280
|
-
!!condition
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
def evaluate_time(time_config)
|
|
285
|
-
case time_config
|
|
286
|
-
when Proc
|
|
287
|
-
@workflow.instance_exec(&time_config)
|
|
288
|
-
when Time
|
|
289
|
-
time_config
|
|
290
|
-
else
|
|
291
|
-
raise ArgumentError, "Schedule time must be a Time or Proc, got #{time_config.class}"
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
def apply_backoff(current_interval, max_interval)
|
|
296
|
-
new_interval = current_interval * @config.backoff
|
|
297
|
-
|
|
298
|
-
if max_interval
|
|
299
|
-
[new_interval, max_interval].min
|
|
300
|
-
else
|
|
301
|
-
new_interval
|
|
302
|
-
end
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def sleep_with_interruption(duration)
|
|
306
|
-
# Use Async.sleep if available, otherwise Kernel.sleep
|
|
307
|
-
if defined?(::Async::Task) && ::Async::Task.current?
|
|
308
|
-
::Async::Task.current.sleep(duration)
|
|
309
|
-
else
|
|
310
|
-
Kernel.sleep(duration)
|
|
311
|
-
end
|
|
312
|
-
end
|
|
313
|
-
end
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
end
|
|
317
|
-
end
|