swarm_memory 2.1.5 → 2.1.6

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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. metadata +5 -184
  4. data/lib/claude_swarm/base_executor.rb +0 -133
  5. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  6. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  7. data/lib/claude_swarm/cli.rb +0 -697
  8. data/lib/claude_swarm/commands/ps.rb +0 -215
  9. data/lib/claude_swarm/commands/show.rb +0 -139
  10. data/lib/claude_swarm/configuration.rb +0 -373
  11. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  12. data/lib/claude_swarm/json_handler.rb +0 -91
  13. data/lib/claude_swarm/mcp_generator.rb +0 -230
  14. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  15. data/lib/claude_swarm/openai/executor.rb +0 -256
  16. data/lib/claude_swarm/openai/responses.rb +0 -319
  17. data/lib/claude_swarm/orchestrator.rb +0 -878
  18. data/lib/claude_swarm/process_tracker.rb +0 -78
  19. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  20. data/lib/claude_swarm/session_path.rb +0 -42
  21. data/lib/claude_swarm/settings_generator.rb +0 -77
  22. data/lib/claude_swarm/system_utils.rb +0 -46
  23. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  24. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  25. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  27. data/lib/claude_swarm/version.rb +0 -5
  28. data/lib/claude_swarm/worktree_manager.rb +0 -475
  29. data/lib/claude_swarm/yaml_loader.rb +0 -22
  30. data/lib/claude_swarm.rb +0 -67
  31. data/lib/swarm_cli/cli.rb +0 -201
  32. data/lib/swarm_cli/command_registry.rb +0 -61
  33. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  34. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  35. data/lib/swarm_cli/commands/migrate.rb +0 -55
  36. data/lib/swarm_cli/commands/run.rb +0 -173
  37. data/lib/swarm_cli/config_loader.rb +0 -98
  38. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  39. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  40. data/lib/swarm_cli/interactive_repl.rb +0 -924
  41. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  42. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  43. data/lib/swarm_cli/migrate_options.rb +0 -54
  44. data/lib/swarm_cli/migrator.rb +0 -132
  45. data/lib/swarm_cli/options.rb +0 -151
  46. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  47. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  48. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  49. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  50. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  51. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  52. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  53. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  54. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  55. data/lib/swarm_cli/ui/icons.rb +0 -36
  56. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  57. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  58. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  59. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  60. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  61. data/lib/swarm_cli/version.rb +0 -5
  62. data/lib/swarm_cli.rb +0 -46
  63. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  64. data/lib/swarm_sdk/agent/builder.rb +0 -552
  65. data/lib/swarm_sdk/agent/chat.rb +0 -774
  66. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  67. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  68. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  69. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  70. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  71. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  72. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  73. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  75. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  76. data/lib/swarm_sdk/agent/context.rb +0 -116
  77. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  78. data/lib/swarm_sdk/agent/definition.rb +0 -477
  79. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  80. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  81. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  82. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  83. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  84. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  85. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  86. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  87. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  88. data/lib/swarm_sdk/configuration.rb +0 -135
  89. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  90. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  91. data/lib/swarm_sdk/context_compactor.rb +0 -335
  92. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  93. data/lib/swarm_sdk/context_management/context.rb +0 -328
  94. data/lib/swarm_sdk/defaults.rb +0 -196
  95. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  96. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  97. data/lib/swarm_sdk/hooks/context.rb +0 -197
  98. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  99. data/lib/swarm_sdk/hooks/error.rb +0 -29
  100. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  101. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  102. data/lib/swarm_sdk/hooks/result.rb +0 -150
  103. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  104. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  105. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  106. data/lib/swarm_sdk/log_collector.rb +0 -227
  107. data/lib/swarm_sdk/log_stream.rb +0 -127
  108. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  109. data/lib/swarm_sdk/model_aliases.json +0 -8
  110. data/lib/swarm_sdk/models.json +0 -1
  111. data/lib/swarm_sdk/models.rb +0 -120
  112. data/lib/swarm_sdk/node_context.rb +0 -245
  113. data/lib/swarm_sdk/observer/builder.rb +0 -81
  114. data/lib/swarm_sdk/observer/config.rb +0 -45
  115. data/lib/swarm_sdk/observer/manager.rb +0 -236
  116. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  117. data/lib/swarm_sdk/permissions/config.rb +0 -239
  118. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  119. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  120. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  121. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  122. data/lib/swarm_sdk/plugin.rb +0 -309
  123. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  124. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  125. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  126. data/lib/swarm_sdk/restore_result.rb +0 -65
  127. data/lib/swarm_sdk/result.rb +0 -123
  128. data/lib/swarm_sdk/snapshot.rb +0 -156
  129. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  130. data/lib/swarm_sdk/state_restorer.rb +0 -476
  131. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  132. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  133. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  134. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  135. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  136. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  141. data/lib/swarm_sdk/swarm.rb +0 -717
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/bash.rb +0 -282
  145. data/lib/swarm_sdk/tools/clock.rb +0 -44
  146. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  147. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  148. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  149. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  150. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  151. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  152. data/lib/swarm_sdk/tools/edit.rb +0 -145
  153. data/lib/swarm_sdk/tools/glob.rb +0 -166
  154. data/lib/swarm_sdk/tools/grep.rb +0 -235
  155. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  156. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  157. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -98
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/utils.rb +0 -68
  174. data/lib/swarm_sdk/validation_result.rb +0 -33
  175. data/lib/swarm_sdk/version.rb +0 -5
  176. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  177. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  178. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  179. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  180. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  181. data/lib/swarm_sdk/workflow.rb +0 -554
  182. data/lib/swarm_sdk.rb +0 -524
@@ -1,555 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- class Workflow
5
- # NodeBuilder provides DSL for configuring individual nodes within a workflow
6
- #
7
- # A node represents a stage in a multi-step workflow where a specific set
8
- # of agents collaborate. Each node creates an independent swarm execution.
9
- #
10
- # @example Solo agent node
11
- # node :planning do
12
- # agent(:architect)
13
- # end
14
- #
15
- # @example Multi-agent node with delegation
16
- # node :implementation do
17
- # agent(:backend).delegates_to(:tester, :database)
18
- # agent(:tester).delegates_to(:database)
19
- # agent(:database)
20
- #
21
- # depends_on :planning
22
- # end
23
- class NodeBuilder
24
- attr_reader :name,
25
- :agent_configs,
26
- :dependencies,
27
- :lead_override,
28
- :input_transformer,
29
- :output_transformer,
30
- :input_transformer_command,
31
- :output_transformer_command
32
-
33
- def initialize(name)
34
- @name = name
35
- @agent_configs = []
36
- @dependencies = []
37
- @lead_override = nil
38
- @input_transformer = nil # Ruby block
39
- @output_transformer = nil # Ruby block
40
- @input_transformer_command = nil # Bash command
41
- @output_transformer_command = nil # Bash command
42
- end
43
-
44
- # Configure an agent for this node
45
- #
46
- # Returns an AgentConfig object that supports fluent delegation and tool override syntax.
47
- # If delegates_to/tools are not called, the agent uses global configuration.
48
- #
49
- # By default, agents get fresh context in each node (reset_context: true).
50
- # Set reset_context: false to preserve conversation history across nodes.
51
- #
52
- # @param name [Symbol] Agent name
53
- # @param reset_context [Boolean] Whether to reset agent context (default: true)
54
- # @return [AgentConfig] Fluent configuration object
55
- #
56
- # @example With delegation
57
- # agent(:backend).delegates_to(:tester, :database)
58
- #
59
- # @example Without delegation
60
- # agent(:planner)
61
- #
62
- # @example Preserve context across nodes
63
- # agent(:architect, reset_context: false)
64
- #
65
- # @example Override tools for this node
66
- # agent(:backend).tools(:Read, :Think)
67
- #
68
- # @example Combine delegation and tools
69
- # agent(:backend).delegates_to(:tester).tools(:Read, :Edit, :Write)
70
- def agent(name, reset_context: true)
71
- config = AgentConfig.new(name, self, reset_context: reset_context)
72
-
73
- # Register immediately with empty delegation and no tool override
74
- # If delegates_to/tools are called later, they will update this
75
- register_agent(name, [], reset_context, nil)
76
-
77
- config
78
- end
79
-
80
- # Register an agent configuration (called by AgentConfig)
81
- #
82
- # @param agent_name [Symbol] Agent name
83
- # @param delegates_to [Array<Symbol>] Delegation targets
84
- # @param reset_context [Boolean] Whether to reset agent context
85
- # @param tools [Array<Symbol>, nil] Tool override for this node (nil = use global)
86
- # @return [void]
87
- def register_agent(agent_name, delegates_to, reset_context = true, tools = nil)
88
- # Check if agent already registered
89
- existing = @agent_configs.find { |ac| ac[:agent] == agent_name }
90
-
91
- if existing
92
- # Update delegation, reset_context, and tools (happens when methods are called after agent())
93
- existing[:delegates_to] = delegates_to
94
- existing[:reset_context] = reset_context
95
- existing[:tools] = tools unless tools.nil?
96
- else
97
- # Add new agent configuration
98
- @agent_configs << {
99
- agent: agent_name,
100
- delegates_to: delegates_to,
101
- reset_context: reset_context,
102
- tools: tools,
103
- }
104
- end
105
- end
106
-
107
- # Declare dependencies (nodes that must execute before this one)
108
- #
109
- # @param node_names [Array<Symbol>] Names of prerequisite nodes
110
- # @return [void]
111
- #
112
- # @example Single dependency
113
- # depends_on :planning
114
- #
115
- # @example Multiple dependencies
116
- # depends_on :frontend, :backend
117
- def depends_on(*node_names)
118
- @dependencies.concat(node_names.map(&:to_sym))
119
- end
120
-
121
- # Override the lead agent (first agent is lead by default)
122
- #
123
- # @param agent_name [Symbol] Name of agent to make lead
124
- # @return [void]
125
- #
126
- # @example
127
- # agent(:backend).delegates_to(:tester)
128
- # agent(:tester)
129
- # lead :tester # tester is lead instead of backend
130
- def lead(agent_name)
131
- @lead_override = agent_name.to_sym
132
- end
133
-
134
- # Define input transformer for this node
135
- #
136
- # The transformer receives a NodeContext object with access to:
137
- # - Previous node's result (convenience: ctx.content)
138
- # - Original user prompt (ctx.original_prompt)
139
- # - All previous node results (ctx.all_results[:node_name])
140
- # - Current node metadata (ctx.node_name, ctx.dependencies)
141
- #
142
- # Can also be used for side effects (logging, file I/O) since the block
143
- # runs at execution time, not declaration time.
144
- #
145
- # **Control Flow**: Return a hash with special keys to control execution:
146
- # - `skip_execution: true` - Skip node's LLM execution, return content immediately
147
- # - `halt_workflow: true` - Halt entire workflow with content as final result
148
- # - `goto_node: :node_name` - Jump to different node with content as input
149
- #
150
- # @yield [NodeContext] Context with previous results and metadata
151
- # @return [String, Hash] Transformed input OR control hash
152
- #
153
- # @example Access previous result and original prompt
154
- # input do |ctx|
155
- # # Convenience accessor
156
- # previous_content = ctx.content
157
- #
158
- # # Access original prompt
159
- # "Original: #{ctx.original_prompt}\nPrevious: #{previous_content}"
160
- # end
161
- #
162
- # @example Access results from specific nodes
163
- # input do |ctx|
164
- # plan = ctx.all_results[:planning].content
165
- # design = ctx.all_results[:design].content
166
- #
167
- # "Implement based on:\nPlan: #{plan}\nDesign: #{design}"
168
- # end
169
- #
170
- # @example Skip execution (caching) - using return
171
- # input do |ctx|
172
- # cached = check_cache(ctx.content)
173
- # return ctx.skip_execution(content: cached) if cached
174
- # ctx.content
175
- # end
176
- #
177
- # @example Halt workflow (validation) - using return
178
- # input do |ctx|
179
- # if ctx.content.length > 10000
180
- # # Halt entire workflow - return works safely!
181
- # return ctx.halt_workflow(content: "ERROR: Input too long")
182
- # end
183
- # ctx.content
184
- # end
185
- #
186
- # @example Jump to different node (conditional routing) - using return
187
- # input do |ctx|
188
- # if ctx.content.include?("NEEDS_REVIEW")
189
- # # Jump to review node instead - return works safely!
190
- # return ctx.goto_node(:review, content: ctx.content)
191
- # end
192
- # ctx.content
193
- # end
194
- #
195
- # @note The input block is automatically converted to a lambda, which means
196
- # return statements work safely and only exit the transformer, not the
197
- # entire program. This allows natural control flow patterns.
198
- def input(&block)
199
- @input_transformer = ProcHelpers.to_lambda(block)
200
- end
201
-
202
- # Set input transformer as bash command (YAML API)
203
- #
204
- # The command receives NodeContext as JSON on STDIN and outputs transformed content.
205
- #
206
- # **Exit codes:**
207
- # - 0: Success, use STDOUT as transformed content
208
- # - 1: Skip node execution, use current_input unchanged (STDOUT ignored)
209
- # - 2: Halt workflow with error, show STDERR (STDOUT ignored)
210
- #
211
- # @param command [String] Bash command to execute
212
- # @param timeout [Integer] Timeout in seconds (default: 60)
213
- # @return [void]
214
- #
215
- # @example
216
- # input_command("scripts/validate.sh", timeout: 30)
217
- def input_command(command, timeout: TransformerExecutor::DEFAULT_TIMEOUT)
218
- @input_transformer_command = { command: command, timeout: timeout }
219
- end
220
-
221
- # Define output transformer for this node
222
- #
223
- # The transformer receives a NodeContext object with access to:
224
- # - Current node's result (convenience: ctx.content)
225
- # - Original user prompt (ctx.original_prompt)
226
- # - All completed node results (ctx.all_results[:node_name])
227
- # - Current node metadata (ctx.node_name)
228
- #
229
- # Can also be used for side effects (logging, file I/O) since the block
230
- # runs at execution time, not declaration time.
231
- #
232
- # **Control Flow**: Return a hash with special keys to control execution:
233
- # - `halt_workflow: true` - Halt entire workflow with content as final result
234
- # - `goto_node: :node_name` - Jump to different node with content as input
235
- #
236
- # @yield [NodeContext] Context with current result and metadata
237
- # @return [String, Hash] Transformed output OR control hash
238
- #
239
- # @example Transform and save to file
240
- # output do |ctx|
241
- # # Side effect: save to file
242
- # File.write("results/plan.txt", ctx.content)
243
- #
244
- # # Return transformed output for next node
245
- # "Key decisions: #{extract_decisions(ctx.content)}"
246
- # end
247
- #
248
- # @example Access original prompt
249
- # output do |ctx|
250
- # # Include original context in output
251
- # "Task: #{ctx.original_prompt}\nResult: #{ctx.content}"
252
- # end
253
- #
254
- # @example Halt workflow (convergence check) - using return
255
- # output do |ctx|
256
- # return ctx.halt_workflow(content: ctx.content) if converged?(ctx.content)
257
- # ctx.content
258
- # end
259
- #
260
- # @example Jump to different node (conditional routing) - using return
261
- # output do |ctx|
262
- # if needs_revision?(ctx.content)
263
- # # Go back to revision node - return works safely!
264
- # return ctx.goto_node(:revision, content: ctx.content)
265
- # end
266
- # ctx.content
267
- # end
268
- #
269
- # @note The output block is automatically converted to a lambda, which means
270
- # return statements work safely and only exit the transformer, not the
271
- # entire program. This allows natural control flow patterns.
272
- def output(&block)
273
- @output_transformer = ProcHelpers.to_lambda(block)
274
- end
275
-
276
- # Set output transformer as bash command (YAML API)
277
- #
278
- # The command receives NodeContext as JSON on STDIN and outputs transformed content.
279
- #
280
- # **Exit codes:**
281
- # - 0: Success, use STDOUT as transformed content
282
- # - 1: Pass through unchanged, use result.content (STDOUT ignored)
283
- # - 2: Halt workflow with error, show STDERR (STDOUT ignored)
284
- #
285
- # @param command [String] Bash command to execute
286
- # @param timeout [Integer] Timeout in seconds (default: 60)
287
- # @return [void]
288
- #
289
- # @example
290
- # output_command("scripts/format.sh", timeout: 30)
291
- def output_command(command, timeout: TransformerExecutor::DEFAULT_TIMEOUT)
292
- @output_transformer_command = { command: command, timeout: timeout }
293
- end
294
-
295
- # Check if node has any input transformer (block or command)
296
- #
297
- # @return [Boolean]
298
- def has_input_transformer?
299
- @input_transformer || @input_transformer_command
300
- end
301
-
302
- # Check if node has any output transformer (block or command)
303
- #
304
- # @return [Boolean]
305
- def has_output_transformer?
306
- @output_transformer || @output_transformer_command
307
- end
308
-
309
- # Transform input using configured transformer (block or command)
310
- #
311
- # Executes either Ruby block or bash command transformer.
312
- #
313
- # **Ruby block return values:**
314
- # - String: Transformed content
315
- # - Hash with `skip_execution: true`: Skip node execution
316
- # - Hash with `halt_workflow: true`: Halt entire workflow
317
- # - Hash with `goto_node: :name`: Jump to different node
318
- #
319
- # **Exit code behavior (bash commands only):**
320
- # - Exit 0: Use STDOUT as transformed content
321
- # - Exit 1: Skip node execution, use current_input unchanged (STDOUT ignored)
322
- # - Exit 2: Halt workflow with error (STDOUT ignored)
323
- #
324
- # @param context [NodeContext] Context with previous results and metadata
325
- # @param current_input [String] Fallback content for exit 1 (skip), also used for halt error context
326
- # @return [String, Hash] Transformed input OR control hash (skip_execution, halt_workflow, goto_node)
327
- # @raise [ConfigurationError] If bash transformer halts workflow (exit 2)
328
- def transform_input(context, current_input:)
329
- # No transformer configured: return content as-is
330
- return context.content unless @input_transformer || @input_transformer_command
331
-
332
- # Ruby block transformer
333
- # Ruby blocks can return String (transformed content) OR Hash (control flow)
334
- if @input_transformer
335
- result = @input_transformer.call(context)
336
-
337
- # If hash, validate control flow keys
338
- if result.is_a?(Hash)
339
- validate_transformer_hash(result, :input)
340
- end
341
-
342
- return result
343
- end
344
-
345
- # Bash command transformer
346
- # Bash commands use exit codes to control behavior:
347
- # - Exit 0: Success, use STDOUT as transformed content
348
- # - Exit 1: Skip node execution, use current_input unchanged (STDOUT ignored)
349
- # - Exit 2: Halt workflow with error (STDOUT ignored)
350
- if @input_transformer_command
351
- result = TransformerExecutor.execute(
352
- command: @input_transformer_command[:command],
353
- context: context,
354
- event: "input",
355
- node_name: @name,
356
- fallback_content: current_input, # Used for exit 1 (skip)
357
- timeout: @input_transformer_command[:timeout],
358
- )
359
-
360
- # Handle transformer result based on exit code
361
- if result.halt?
362
- # Exit 2: Halt workflow with error
363
- raise ConfigurationError,
364
- "Input transformer halted workflow for node '#{@name}': #{result.error_message}"
365
- elsif result.skip_execution?
366
- # Exit 1: Skip node execution, return skip hash
367
- # Content is current_input unchanged (STDOUT was ignored)
368
- { skip_execution: true, content: result.content }
369
- else
370
- # Exit 0: Return transformed content from STDOUT
371
- result.content
372
- end
373
- end
374
- end
375
-
376
- # Transform output using configured transformer (block or command)
377
- #
378
- # Executes either Ruby block or bash command transformer.
379
- #
380
- # **Ruby block return values:**
381
- # - String: Transformed content
382
- # - Hash with `halt_workflow: true`: Halt entire workflow
383
- # - Hash with `goto_node: :name`: Jump to different node
384
- #
385
- # **Exit code behavior (bash commands only):**
386
- # - Exit 0: Use STDOUT as transformed content
387
- # - Exit 1: Pass through unchanged, use result.content (STDOUT ignored)
388
- # - Exit 2: Halt workflow with error (STDOUT ignored)
389
- #
390
- # @param context [NodeContext] Context with current result and metadata
391
- # @return [String, Hash] Transformed output OR control hash (halt_workflow, goto_node)
392
- # @raise [ConfigurationError] If bash transformer halts workflow (exit 2)
393
- def transform_output(context)
394
- # No transformer configured: return content as-is
395
- return context.content unless @output_transformer || @output_transformer_command
396
-
397
- # Ruby block transformer
398
- # Ruby blocks can return String (transformed content) OR Hash (control flow)
399
- if @output_transformer
400
- result = @output_transformer.call(context)
401
-
402
- # If hash, validate control flow keys
403
- if result.is_a?(Hash)
404
- validate_transformer_hash(result, :output)
405
- end
406
-
407
- return result
408
- end
409
-
410
- # Bash command transformer
411
- # Bash commands use exit codes to control behavior:
412
- # - Exit 0: Success, use STDOUT as transformed content
413
- # - Exit 1: Pass through unchanged, use result.content (STDOUT ignored)
414
- # - Exit 2: Halt workflow with error from STDERR (STDOUT ignored)
415
- if @output_transformer_command
416
- result = TransformerExecutor.execute(
417
- command: @output_transformer_command[:command],
418
- context: context,
419
- event: "output",
420
- node_name: @name,
421
- fallback_content: context.content, # result.content for exit 1
422
- timeout: @output_transformer_command[:timeout],
423
- )
424
-
425
- # Handle transformer result based on exit code
426
- if result.halt?
427
- # Exit 2: Halt workflow with error
428
- raise ConfigurationError,
429
- "Output transformer halted workflow for node '#{@name}': #{result.error_message}"
430
- else
431
- # Exit 0: Return transformed content from STDOUT
432
- # Exit 1: Return fallback (result.content unchanged)
433
- result.content
434
- end
435
- end
436
- end
437
-
438
- # Get the lead agent for this node
439
- #
440
- # @return [Symbol] Lead agent name
441
- def lead_agent
442
- @lead_override || @agent_configs.first&.dig(:agent)
443
- end
444
-
445
- # Check if this is an agent-less (computation-only) node
446
- #
447
- # Agent-less nodes run pure Ruby code without LLM execution.
448
- # They must have at least one transformer (input or output).
449
- #
450
- # @return [Boolean]
451
- def agent_less?
452
- @agent_configs.empty?
453
- end
454
-
455
- # Validate node configuration
456
- #
457
- # Also auto-adds agents that are referenced in delegates_to but not explicitly declared.
458
- # This allows writing: agent(:backend).delegates_to(:verifier)
459
- # without needing: agent(:verifier)
460
- #
461
- # @return [void]
462
- # @raise [ConfigurationError] If configuration is invalid
463
- def validate!
464
- # Auto-add agents mentioned in delegates_to but not explicitly declared
465
- auto_add_delegate_agents
466
-
467
- # Agent-less nodes (pure computation) are allowed but need transformers
468
- if @agent_configs.empty?
469
- unless has_input_transformer? || has_output_transformer?
470
- raise ConfigurationError,
471
- "Agent-less node '#{@name}' must have at least one transformer (input or output). " \
472
- "Either add agents with agent(:name) or add input/output transformers."
473
- end
474
- end
475
-
476
- # If has agents, validate lead override
477
- if @lead_override && !@agent_configs.any? { |ac| ac[:agent] == @lead_override }
478
- raise ConfigurationError,
479
- "Node '#{@name}' lead agent '#{@lead_override}' not found in node's agents"
480
- end
481
- end
482
-
483
- private
484
-
485
- # Validate transformer hash return value
486
- #
487
- # Ensures hash has valid control flow keys and required content field.
488
- #
489
- # @param hash [Hash] Hash returned from transformer
490
- # @param transformer_type [Symbol] :input or :output
491
- # @return [void]
492
- # @raise [ConfigurationError] If hash is invalid
493
- def validate_transformer_hash(hash, transformer_type)
494
- # Valid control keys
495
- valid_keys = if transformer_type == :input
496
- [:skip_execution, :halt_workflow, :goto_node, :content]
497
- else
498
- [:halt_workflow, :goto_node, :content]
499
- end
500
-
501
- # Check for invalid keys
502
- invalid_keys = hash.keys - valid_keys
503
- if invalid_keys.any?
504
- raise ConfigurationError,
505
- "Invalid #{transformer_type} transformer hash keys: #{invalid_keys.join(", ")}. " \
506
- "Valid keys: #{valid_keys.join(", ")}"
507
- end
508
-
509
- # Ensure content is present
510
- unless hash.key?(:content)
511
- raise ConfigurationError,
512
- "#{transformer_type.capitalize} transformer hash must include :content key"
513
- end
514
-
515
- # Ensure only one control key
516
- control_keys = hash.keys & [:skip_execution, :halt_workflow, :goto_node]
517
- if control_keys.size > 1
518
- raise ConfigurationError,
519
- "#{transformer_type.capitalize} transformer hash can only have one control key, got: #{control_keys.join(", ")}"
520
- end
521
-
522
- # Validate goto_node has valid node name
523
- if hash[:goto_node] && !hash[:goto_node].is_a?(Symbol)
524
- raise ConfigurationError,
525
- "goto_node value must be a Symbol, got: #{hash[:goto_node].class}"
526
- end
527
- end
528
-
529
- # Auto-add agents that are mentioned in delegates_to but not explicitly declared
530
- #
531
- # This allows:
532
- # agent(:backend).delegates_to(:tester)
533
- # Without needing:
534
- # agent(:tester)
535
- #
536
- # The tester agent is automatically added to the node with no delegation
537
- # and reset_context: true (fresh context by default).
538
- #
539
- # @return [void]
540
- def auto_add_delegate_agents
541
- # Collect all agents mentioned in delegates_to
542
- all_delegates = @agent_configs.flat_map { |ac| ac[:delegates_to] }.uniq
543
-
544
- # Find delegates that aren't explicitly declared
545
- declared_agents = @agent_configs.map { |ac| ac[:agent] }
546
- missing_delegates = all_delegates - declared_agents
547
-
548
- # Auto-add missing delegates with empty delegation and default reset_context
549
- missing_delegates.each do |delegate_name|
550
- @agent_configs << { agent: delegate_name, delegates_to: [], reset_context: true }
551
- end
552
- end
553
- end
554
- end
555
- end