standard_procedure_operations 0.5.2 → 0.6.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +112 -356
  3. data/app/jobs/operations/agent/find_timeouts_job.rb +5 -0
  4. data/app/jobs/operations/agent/runner_job.rb +5 -0
  5. data/app/jobs/operations/agent/timeout_job.rb +5 -0
  6. data/app/jobs/operations/agent/wake_agents_job.rb +5 -0
  7. data/app/models/concerns/operations/participant.rb +1 -1
  8. data/app/models/operations/agent/interaction_handler.rb +30 -0
  9. data/app/models/operations/agent/plan.rb +38 -0
  10. data/app/models/operations/agent/runner.rb +37 -0
  11. data/app/models/operations/{task/state_management → agent}/wait_handler.rb +4 -2
  12. data/app/models/operations/agent.rb +31 -0
  13. data/app/models/operations/task/data_carrier.rb +0 -2
  14. data/app/models/operations/task/exports.rb +4 -4
  15. data/app/models/operations/task/{state_management → plan}/action_handler.rb +3 -1
  16. data/app/models/operations/task/{state_management → plan}/decision_handler.rb +3 -1
  17. data/app/models/operations/task/{state_management/completion_handler.rb → plan/result_handler.rb} +3 -1
  18. data/app/models/operations/task/{state_management.rb → plan.rb} +2 -4
  19. data/app/models/operations/task/testing.rb +2 -1
  20. data/app/models/operations/task.rb +46 -30
  21. data/app/models/operations/task_participant.rb +2 -0
  22. data/db/migrate/{20250403075414_add_becomes_zombie_at_field.rb → 20250404085321_add_becomes_zombie_at_field.operations.rb} +1 -0
  23. data/db/migrate/20250407143513_agent_fields.rb +9 -0
  24. data/db/migrate/20250408124423_add_task_participant_indexes.rb +5 -0
  25. data/lib/operations/exporters/svg.rb +399 -0
  26. data/lib/operations/has_data_attributes.rb +50 -0
  27. data/lib/operations/invalid_state.rb +2 -0
  28. data/lib/operations/version.rb +1 -1
  29. data/lib/operations.rb +3 -2
  30. data/lib/tasks/operations_tasks.rake +3 -3
  31. metadata +21 -12
  32. data/app/jobs/operations/task_runner_job.rb +0 -11
  33. data/app/models/operations/task/background.rb +0 -39
  34. data/lib/operations/cannot_wait_in_foreground.rb +0 -2
  35. data/lib/operations/exporters/graphviz.rb +0 -164
@@ -1,164 +0,0 @@
1
- require "graphviz"
2
-
3
- module Operations
4
- module Exporters
5
- class Graphviz
6
- attr_reader :task_class
7
-
8
- def self.export(task_class)
9
- new(task_class).to_dot
10
- end
11
-
12
- def initialize(task_class)
13
- @task_class = task_class
14
- end
15
-
16
- # Generate a DOT representation of the task flow
17
- def to_dot
18
- graph.output(dot: String)
19
- end
20
-
21
- # Generate and save the graph to a file
22
- # Supported formats: dot, png, svg, pdf, etc.
23
- def save(filename, format: :png)
24
- graph.output(format => filename)
25
- end
26
-
27
- # Generate GraphViz object representing the task flow
28
- def graph
29
- @graph ||= build_graph
30
- end
31
-
32
- private
33
-
34
- def build_graph
35
- task_hash = task_class.to_h
36
- g = GraphViz.new(:G, type: :digraph, rankdir: "LR")
37
-
38
- # Set up node styles
39
- g.node[:shape] = "box"
40
- g.node[:style] = "rounded"
41
- g.node[:fontname] = "Arial"
42
- g.node[:fontsize] = "12"
43
- g.edge[:fontname] = "Arial"
44
- g.edge[:fontsize] = "10"
45
-
46
- # Create nodes for each state
47
- nodes = {}
48
- task_hash[:states].each do |state_name, state_info|
49
- node_style = node_style_for(state_info[:type])
50
- node_label = create_node_label(state_name, state_info)
51
- nodes[state_name] = g.add_nodes(state_name.to_s, label: node_label, **node_style)
52
- end
53
-
54
- # Add edges for transitions
55
- task_hash[:states].each do |state_name, state_info|
56
- case state_info[:type]
57
- when :decision
58
- add_decision_edges(g, nodes, state_name, state_info[:transitions])
59
- when :action
60
- if state_info[:next_state]
61
- g.add_edges(nodes[state_name], nodes[state_info[:next_state]])
62
- end
63
- when :wait
64
- add_wait_edges(g, nodes, state_name, state_info[:transitions])
65
- end
66
- end
67
-
68
- # Mark initial state
69
- if nodes[task_hash[:initial_state]]
70
- initial_node = g.add_nodes("START", shape: "circle", style: "filled", fillcolor: "#59a14f", fontcolor: "white")
71
- g.add_edges(initial_node, nodes[task_hash[:initial_state]])
72
- end
73
-
74
- g
75
- end
76
-
77
- def node_style_for(type)
78
- case type
79
- when :decision
80
- {shape: "diamond", style: "filled", fillcolor: "#4e79a7", fontcolor: "white"}
81
- when :action
82
- {shape: "box", style: "filled", fillcolor: "#f28e2b", fontcolor: "white"}
83
- when :wait
84
- {shape: "box", style: "filled,dashed", fillcolor: "#76b7b2", fontcolor: "white"}
85
- when :result
86
- {shape: "box", style: "filled", fillcolor: "#59a14f", fontcolor: "white"}
87
- else
88
- {shape: "box"}
89
- end
90
- end
91
-
92
- def create_node_label(state_name, state_info)
93
- label = state_name.to_s
94
-
95
- # Add inputs if present
96
- if state_info[:inputs].present?
97
- inputs_list = state_info[:inputs].map(&:to_s).join(", ")
98
- label += "\nInputs: #{inputs_list}"
99
- end
100
-
101
- # Add optional inputs if present
102
- if state_info[:optional_inputs].present?
103
- optional_list = state_info[:optional_inputs].map(&:to_s).join(", ")
104
- label += "\nOptional: #{optional_list}"
105
- end
106
-
107
- label
108
- end
109
-
110
- def add_decision_edges(graph, nodes, state_name, transitions)
111
- # Get the handler for this state to access condition labels
112
- task_state = task_class.respond_to?(:states) ? task_class.states[state_name.to_sym] : nil
113
- handler = task_state[:handler] if task_state
114
-
115
- transitions.each_with_index do |(condition, target), index|
116
- # Get custom label if available
117
- label = (handler&.respond_to?(:condition_labels) && handler.condition_labels[index]) ? handler.condition_labels[index] : target.to_s
118
-
119
- if (target.is_a?(Symbol) || target.is_a?(String)) && nodes[target.to_sym]
120
- graph.add_edges(nodes[state_name], nodes[target.to_sym], label: label)
121
- elsif target.respond_to?(:call)
122
- # Create a special node to represent the custom action
123
- block_node_name = "#{state_name}_#{condition}_block"
124
- block_node = graph.add_nodes(block_node_name,
125
- label: "#{condition} Custom Action",
126
- shape: "note",
127
- style: "filled",
128
- fillcolor: "#bab0ab",
129
- fontcolor: "black")
130
-
131
- graph.add_edges(nodes[state_name], block_node,
132
- label: label,
133
- style: "dashed")
134
- end
135
- end
136
- end
137
-
138
- def add_wait_edges(graph, nodes, state_name, transitions)
139
- # Get the handler for this state to access condition labels
140
- task_state = task_class.respond_to?(:states) ? task_class.states[state_name.to_sym] : nil
141
- handler = task_state[:handler] if task_state
142
-
143
- # Add a self-loop for wait condition
144
- graph.add_edges(nodes[state_name], nodes[state_name],
145
- label: "waiting",
146
- style: "dashed",
147
- constraint: "false",
148
- dir: "back")
149
-
150
- # Add edges for each transition
151
- transitions.each_with_index do |(condition, target), index|
152
- # Get custom label if available
153
- label = (handler&.respond_to?(:condition_labels) && handler.condition_labels[index]) ? handler.condition_labels[index] : target.to_s
154
-
155
- if (target.is_a?(Symbol) || target.is_a?(String)) && nodes[target.to_sym]
156
- graph.add_edges(nodes[state_name], nodes[target.to_sym],
157
- label: label,
158
- style: "solid")
159
- end
160
- end
161
- end
162
- end
163
- end
164
- end