swarm_sdk 2.0.0.pre.2

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 (68) hide show
  1. checksums.yaml +7 -0
  2. data/lib/swarm_sdk/agent/builder.rb +333 -0
  3. data/lib/swarm_sdk/agent/chat/context_tracker.rb +271 -0
  4. data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
  5. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +99 -0
  6. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +114 -0
  7. data/lib/swarm_sdk/agent/chat.rb +779 -0
  8. data/lib/swarm_sdk/agent/context.rb +108 -0
  9. data/lib/swarm_sdk/agent/definition.rb +335 -0
  10. data/lib/swarm_sdk/configuration.rb +251 -0
  11. data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
  12. data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
  13. data/lib/swarm_sdk/context_compactor.rb +340 -0
  14. data/lib/swarm_sdk/hooks/adapter.rb +359 -0
  15. data/lib/swarm_sdk/hooks/context.rb +163 -0
  16. data/lib/swarm_sdk/hooks/definition.rb +80 -0
  17. data/lib/swarm_sdk/hooks/error.rb +29 -0
  18. data/lib/swarm_sdk/hooks/executor.rb +146 -0
  19. data/lib/swarm_sdk/hooks/registry.rb +143 -0
  20. data/lib/swarm_sdk/hooks/result.rb +150 -0
  21. data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
  22. data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
  23. data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
  24. data/lib/swarm_sdk/log_collector.rb +83 -0
  25. data/lib/swarm_sdk/log_stream.rb +69 -0
  26. data/lib/swarm_sdk/markdown_parser.rb +46 -0
  27. data/lib/swarm_sdk/permissions/config.rb +239 -0
  28. data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
  29. data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
  30. data/lib/swarm_sdk/permissions/validator.rb +173 -0
  31. data/lib/swarm_sdk/permissions_builder.rb +122 -0
  32. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +237 -0
  33. data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
  34. data/lib/swarm_sdk/result.rb +97 -0
  35. data/lib/swarm_sdk/swarm/agent_initializer.rb +224 -0
  36. data/lib/swarm_sdk/swarm/all_agents_builder.rb +62 -0
  37. data/lib/swarm_sdk/swarm/builder.rb +240 -0
  38. data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
  39. data/lib/swarm_sdk/swarm/tool_configurator.rb +267 -0
  40. data/lib/swarm_sdk/swarm.rb +837 -0
  41. data/lib/swarm_sdk/tools/bash.rb +274 -0
  42. data/lib/swarm_sdk/tools/delegate.rb +152 -0
  43. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
  44. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
  45. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
  46. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
  47. data/lib/swarm_sdk/tools/edit.rb +150 -0
  48. data/lib/swarm_sdk/tools/glob.rb +158 -0
  49. data/lib/swarm_sdk/tools/grep.rb +231 -0
  50. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
  51. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
  52. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
  53. data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
  54. data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
  55. data/lib/swarm_sdk/tools/read.rb +251 -0
  56. data/lib/swarm_sdk/tools/registry.rb +73 -0
  57. data/lib/swarm_sdk/tools/scratchpad_list.rb +88 -0
  58. data/lib/swarm_sdk/tools/scratchpad_read.rb +59 -0
  59. data/lib/swarm_sdk/tools/scratchpad_write.rb +88 -0
  60. data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
  61. data/lib/swarm_sdk/tools/stores/scratchpad.rb +153 -0
  62. data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
  63. data/lib/swarm_sdk/tools/todo_write.rb +216 -0
  64. data/lib/swarm_sdk/tools/write.rb +117 -0
  65. data/lib/swarm_sdk/utils.rb +50 -0
  66. data/lib/swarm_sdk/version.rb +5 -0
  67. data/lib/swarm_sdk.rb +69 -0
  68. metadata +169 -0
@@ -0,0 +1,359 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Hooks
5
+ # Translates YAML hooks configuration to Ruby hooks
6
+ #
7
+ # Adapter bridges the gap between declarative YAML hooks (shell commands)
8
+ # and SwarmSDK's internal hook system. It creates hooks that execute
9
+ # shell commands and translate exit codes to Result objects.
10
+ #
11
+ # ## YAML Hooks are YAML-Only
12
+ #
13
+ # Hooks are a **YAML-only feature** designed for users who want Claude Code-style
14
+ # shell command hooks. Users of the Ruby API should use hooks directly.
15
+ #
16
+ # ## Swarm-Level vs Agent-Level
17
+ #
18
+ # - **Swarm-level**: Only `swarm_start` and `swarm_stop` (lifecycle hooks)
19
+ # - **Agent-level**: All other events (per-agent or all_agents)
20
+ # - **all_agents**: Hooks applied as swarm defaults to all agents
21
+ #
22
+ # ## Event Naming
23
+ #
24
+ # Uses snake_case to match internal hook events directly (no translation):
25
+ # - `pre_tool_use` → :pre_tool_use
26
+ # - `swarm_start` → :swarm_start
27
+ # - etc.
28
+ #
29
+ # @example YAML configuration
30
+ # swarm:
31
+ # hooks:
32
+ # swarm_start:
33
+ # - hooks:
34
+ # - type: command
35
+ # command: "echo 'Starting swarm'"
36
+ #
37
+ # all_agents:
38
+ # hooks:
39
+ # pre_tool_use:
40
+ # - matcher: "Write|Edit"
41
+ # hooks:
42
+ # - type: command
43
+ # command: "rubocop --stdin"
44
+ #
45
+ # agents:
46
+ # backend:
47
+ # hooks:
48
+ # pre_tool_use:
49
+ # - matcher: "Bash"
50
+ # hooks:
51
+ # - type: command
52
+ # command: "python validate_bash.py"
53
+ class Adapter
54
+ # Swarm-level events (only these allowed at swarm.hooks level)
55
+ SWARM_LEVEL_EVENTS = [:swarm_start, :swarm_stop].freeze
56
+
57
+ # Agent-level events (allowed in all_agents.hooks and agent.hooks)
58
+ AGENT_LEVEL_EVENTS = [
59
+ :pre_tool_use,
60
+ :post_tool_use,
61
+ :user_prompt,
62
+ :agent_step,
63
+ :agent_stop,
64
+ :first_message,
65
+ :pre_delegation,
66
+ :post_delegation,
67
+ :context_warning,
68
+ ].freeze
69
+
70
+ class << self
71
+ # Apply hooks from YAML configuration to swarm
72
+ #
73
+ # This is called automatically by Swarm.load after creating the swarm instance.
74
+ # It translates YAML hooks into hooks that execute shell commands.
75
+ #
76
+ # @param swarm [Swarm] Swarm instance to configure
77
+ # @param config [Configuration] Parsed YAML configuration
78
+ # @return [void]
79
+ def apply_hooks(swarm, config)
80
+ # 1. Apply swarm-level hooks (from swarm.hooks)
81
+ apply_swarm_hooks(swarm, config.swarm_hooks) if config.swarm_hooks&.any?
82
+
83
+ # 2. Apply all_agents hooks (as swarm defaults)
84
+ apply_all_agents_hooks(swarm, config.all_agents_hooks) if config.all_agents_hooks&.any?
85
+
86
+ # 3. Store agent hooks for later application (after agents are initialized)
87
+ store_agent_hooks(config)
88
+ end
89
+
90
+ # Apply agent-specific hooks to an already-initialized agent
91
+ #
92
+ # This is called during agent initialization for each agent that has hooks configured.
93
+ #
94
+ # @param agent [AgentChat] Agent instance
95
+ # @param agent_name [Symbol] Agent name
96
+ # @param hooks_config [Hash] Hooks configuration from YAML
97
+ # @param swarm_name [String] Swarm name for environment variables
98
+ # @return [void]
99
+ def apply_agent_hooks(agent, agent_name, hooks_config, swarm_name)
100
+ return unless hooks_config&.any?
101
+
102
+ hooks_config.each do |event_name, hook_defs|
103
+ event_symbol = event_name.to_sym
104
+ validate_agent_event!(event_symbol)
105
+
106
+ # Each hook def can have optional matcher
107
+ Array(hook_defs).each do |hook_def|
108
+ matcher = hook_def[:matcher] || hook_def["matcher"]
109
+ hook = create_hook_callback(hook_def, event_symbol, agent_name, swarm_name)
110
+ agent.add_hook(event_symbol, matcher: matcher, &hook)
111
+ end
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ # Apply swarm-level hooks (swarm_start, swarm_stop)
118
+ #
119
+ # @param swarm [Swarm] Swarm instance
120
+ # @param hooks_config [Hash] Hooks configuration
121
+ def apply_swarm_hooks(swarm, hooks_config)
122
+ hooks_config.each do |event_name, hook_defs|
123
+ event_symbol = event_name.to_sym
124
+ validate_swarm_event!(event_symbol)
125
+
126
+ # Each hook def is a direct hash with type, command, timeout
127
+ Array(hook_defs).each do |hook_def|
128
+ hook = create_swarm_hook_callback(hook_def, event_symbol, swarm.name)
129
+ swarm.add_default_callback(event_symbol, &hook)
130
+ end
131
+ end
132
+ end
133
+
134
+ # Apply all_agents hooks as swarm defaults
135
+ #
136
+ # @param swarm [Swarm] Swarm instance
137
+ # @param hooks_config [Hash] Hooks configuration
138
+ def apply_all_agents_hooks(swarm, hooks_config)
139
+ hooks_config.each do |event_name, hook_defs|
140
+ event_symbol = event_name.to_sym
141
+ validate_agent_event!(event_symbol)
142
+
143
+ # Each hook def can have optional matcher
144
+ Array(hook_defs).each do |hook_def|
145
+ matcher = hook_def[:matcher] || hook_def["matcher"]
146
+ hook = create_all_agents_hook_callback(hook_def, event_symbol, swarm.name)
147
+ swarm.add_default_callback(event_symbol, matcher: matcher, &hook)
148
+ end
149
+ end
150
+ end
151
+
152
+ # Store agent hooks in Configuration for later application
153
+ #
154
+ # @param config [Configuration] Configuration instance
155
+ def store_agent_hooks(config)
156
+ # Agent hooks are already stored in AgentDefinition
157
+ # They'll be applied during agent initialization
158
+ end
159
+
160
+ # Create a hook for agent-level hooks
161
+ #
162
+ # @param hook_def [Hash] Hook definition from YAML
163
+ # @param event_symbol [Symbol] Event type
164
+ # @param agent_name [Symbol, String] Agent name
165
+ # @param swarm_name [String] Swarm name
166
+ # @return [Proc] Hook callback
167
+ def create_hook_callback(hook_def, event_symbol, agent_name, swarm_name)
168
+ # Support both string and symbol keys (YAML may be symbolized)
169
+ command = hook_def[:command] || hook_def["command"]
170
+ timeout = hook_def[:timeout] || hook_def["timeout"] || ShellExecutor::DEFAULT_TIMEOUT
171
+
172
+ lambda do |context|
173
+ input_json = build_input_json(context, event_symbol, agent_name)
174
+ ShellExecutor.execute(
175
+ command: command,
176
+ input_json: input_json,
177
+ timeout: timeout,
178
+ agent_name: agent_name,
179
+ swarm_name: swarm_name,
180
+ event: event_symbol,
181
+ )
182
+ end
183
+ end
184
+
185
+ # Create a hook for all_agents hooks
186
+ #
187
+ # @param hook_def [Hash] Hook definition from YAML
188
+ # @param event_symbol [Symbol] Event type
189
+ # @param swarm_name [String] Swarm name
190
+ # @return [Proc] Hook callback
191
+ def create_all_agents_hook_callback(hook_def, event_symbol, swarm_name)
192
+ # Support both string and symbol keys (YAML may be symbolized)
193
+ command = hook_def[:command] || hook_def["command"]
194
+ timeout = hook_def[:timeout] || hook_def["timeout"] || ShellExecutor::DEFAULT_TIMEOUT
195
+
196
+ lambda do |context|
197
+ # Agent name comes from context
198
+ agent_name = context.agent_name
199
+ input_json = build_input_json(context, event_symbol, agent_name)
200
+ ShellExecutor.execute(
201
+ command: command,
202
+ input_json: input_json,
203
+ timeout: timeout,
204
+ agent_name: agent_name,
205
+ swarm_name: swarm_name,
206
+ event: event_symbol,
207
+ )
208
+ end
209
+ end
210
+
211
+ # Create a hook for swarm-level hooks
212
+ #
213
+ # @param hook_def [Hash] Hook definition from YAML
214
+ # @param event_symbol [Symbol] Event type
215
+ # @param swarm_name [String] Swarm name
216
+ # @return [Proc] Hook callback
217
+ def create_swarm_hook_callback(hook_def, event_symbol, swarm_name)
218
+ # Support both string and symbol keys (YAML may be symbolized)
219
+ command = hook_def[:command] || hook_def["command"]
220
+ timeout = hook_def[:timeout] || hook_def["timeout"] || ShellExecutor::DEFAULT_TIMEOUT
221
+
222
+ lambda do |context|
223
+ input_json = build_swarm_input_json(context, event_symbol, swarm_name)
224
+ ShellExecutor.execute(
225
+ command: command,
226
+ input_json: input_json,
227
+ timeout: timeout,
228
+ agent_name: nil,
229
+ swarm_name: swarm_name,
230
+ event: event_symbol,
231
+ )
232
+ end
233
+ end
234
+
235
+ # Build JSON input for agent-level hook scripts
236
+ #
237
+ # @param context [Context] Hook context
238
+ # @param event_symbol [Symbol] Event type
239
+ # @param agent_name [Symbol, String] Agent name
240
+ # @return [Hash] JSON input for hook script
241
+ def build_input_json(context, event_symbol, agent_name)
242
+ base = {
243
+ event: event_symbol.to_s,
244
+ agent: agent_name.to_s,
245
+ }
246
+
247
+ # Add event-specific data
248
+ case event_symbol
249
+ when :pre_tool_use
250
+ base.merge(
251
+ tool: context.tool_call.name,
252
+ parameters: context.tool_call.parameters,
253
+ )
254
+ when :post_tool_use
255
+ # In post_tool_use, we only have tool_result, not tool_call
256
+ # Need to extract tool info from metadata or tool_result
257
+ base.merge(
258
+ result: context.tool_result.content,
259
+ success: context.tool_result.success?,
260
+ tool_call_id: context.tool_result.tool_call_id,
261
+ )
262
+ when :pre_delegation
263
+ base.merge(
264
+ delegation_target: context.delegation_target,
265
+ task: context.metadata[:task],
266
+ )
267
+ when :post_delegation
268
+ base.merge(
269
+ delegation_target: context.delegation_target,
270
+ task: context.metadata[:task],
271
+ result: context.delegation_result,
272
+ )
273
+ when :user_prompt
274
+ base.merge(
275
+ prompt: context.metadata[:prompt],
276
+ message_count: context.metadata[:message_count],
277
+ )
278
+ when :agent_step
279
+ base.merge(
280
+ content: context.metadata[:content],
281
+ tool_calls: context.metadata[:tool_calls],
282
+ finish_reason: context.metadata[:finish_reason],
283
+ usage: context.metadata[:usage],
284
+ )
285
+ when :agent_stop
286
+ base.merge(
287
+ content: context.metadata[:content],
288
+ finish_reason: context.metadata[:finish_reason],
289
+ usage: context.metadata[:usage],
290
+ )
291
+ when :first_message
292
+ base.merge(prompt: context.metadata[:prompt])
293
+ when :context_warning
294
+ base.merge(
295
+ threshold: context.metadata[:threshold],
296
+ percentage: context.metadata[:percentage],
297
+ tokens_used: context.metadata[:tokens_used],
298
+ tokens_remaining: context.metadata[:tokens_remaining],
299
+ )
300
+ else
301
+ base
302
+ end
303
+ end
304
+
305
+ # Build JSON input for swarm-level hook scripts
306
+ #
307
+ # @param context [Context] Hook context
308
+ # @param event_symbol [Symbol] Event type
309
+ # @param swarm_name [String] Swarm name
310
+ # @return [Hash] JSON input for hook script
311
+ def build_swarm_input_json(context, event_symbol, swarm_name)
312
+ base = {
313
+ event: event_symbol.to_s,
314
+ swarm: swarm_name,
315
+ }
316
+
317
+ case event_symbol
318
+ when :swarm_start, :first_message
319
+ base.merge(prompt: context.metadata[:prompt])
320
+ when :swarm_stop
321
+ base.merge(
322
+ success: context.metadata[:success],
323
+ duration: context.metadata[:duration],
324
+ total_cost: context.metadata[:total_cost],
325
+ total_tokens: context.metadata[:total_tokens],
326
+ )
327
+ else
328
+ base
329
+ end
330
+ end
331
+
332
+ # Validate swarm-level event
333
+ #
334
+ # @param event [Symbol] Event to validate
335
+ # @raise [ConfigurationError] if event invalid for swarm level
336
+ def validate_swarm_event!(event)
337
+ return if SWARM_LEVEL_EVENTS.include?(event)
338
+
339
+ raise ConfigurationError,
340
+ "Invalid swarm-level hook event: #{event}. " \
341
+ "Only #{SWARM_LEVEL_EVENTS.join(", ")} are allowed at swarm.hooks level. " \
342
+ "Use all_agents.hooks or agent.hooks for other events."
343
+ end
344
+
345
+ # Validate agent-level event
346
+ #
347
+ # @param event [Symbol] Event to validate
348
+ # @raise [ConfigurationError] if event invalid for agent level
349
+ def validate_agent_event!(event)
350
+ return if AGENT_LEVEL_EVENTS.include?(event)
351
+
352
+ raise ConfigurationError,
353
+ "Invalid agent-level hook event: #{event}. " \
354
+ "Valid events: #{AGENT_LEVEL_EVENTS.join(", ")}"
355
+ end
356
+ end
357
+ end
358
+ end
359
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Hooks
5
+ # Rich context object passed to hook callbacks
6
+ #
7
+ # Provides hooks with comprehensive access to:
8
+ # - Agent information (name, definition)
9
+ # - Tool call details (for tool-related events)
10
+ # - Delegation details (for delegation events)
11
+ # - Swarm context (access to other agents, configuration)
12
+ # - Metadata (arbitrary additional data)
13
+ #
14
+ # The context is read-write, allowing hooks to modify data
15
+ # (e.g., add metadata, modify tool parameters).
16
+ #
17
+ # @example Access tool call information
18
+ # context.tool_call.name # => "Write"
19
+ # context.tool_call.parameters # => { file_path: "...", content: "..." }
20
+ #
21
+ # @example Modify metadata
22
+ # context.metadata[:validated] = true
23
+ # context.metadata[:validation_time] = Time.now
24
+ #
25
+ # @example Check event type
26
+ # if context.tool_event?
27
+ # validate_tool_call(context.tool_call)
28
+ # end
29
+ class Context
30
+ attr_reader :event, :agent_name, :agent_definition, :swarm, :metadata
31
+ attr_accessor :tool_call, :tool_result, :delegation_target, :delegation_result
32
+
33
+ # @param event [Symbol] The event type triggering this hook
34
+ # @param agent_name [String, Symbol] Name of the agent
35
+ # @param agent_definition [SwarmSDK::AgentDefinition, nil] Agent's configuration
36
+ # @param swarm [SwarmSDK::Swarm, nil] Reference to the swarm (if available)
37
+ # @param tool_call [ToolCall, nil] Tool call object (for tool events)
38
+ # @param tool_result [ToolResult, nil] Tool result (for post_tool_use)
39
+ # @param delegation_target [String, Symbol, nil] Target agent name (for delegation events)
40
+ # @param delegation_result [Object, nil] Result from delegation (for post_delegation)
41
+ # @param metadata [Hash] Additional metadata
42
+ def initialize(
43
+ event:,
44
+ agent_name:,
45
+ agent_definition: nil,
46
+ swarm: nil,
47
+ tool_call: nil,
48
+ tool_result: nil,
49
+ delegation_target: nil,
50
+ delegation_result: nil,
51
+ metadata: {}
52
+ )
53
+ @event = event
54
+ @agent_name = agent_name
55
+ @agent_definition = agent_definition
56
+ @swarm = swarm
57
+ @tool_call = tool_call
58
+ @tool_result = tool_result
59
+ @delegation_target = delegation_target
60
+ @delegation_result = delegation_result
61
+ @metadata = metadata
62
+ end
63
+
64
+ # Check if this is a tool-related event
65
+ #
66
+ # @return [Boolean] true if event is pre_tool_use or post_tool_use
67
+ def tool_event?
68
+ [:pre_tool_use, :post_tool_use].include?(@event)
69
+ end
70
+
71
+ # Check if this is a delegation-related event
72
+ #
73
+ # @return [Boolean] true if event is pre_delegation or post_delegation
74
+ def delegation_event?
75
+ [:pre_delegation, :post_delegation].include?(@event)
76
+ end
77
+
78
+ # Get tool name (convenience method)
79
+ #
80
+ # @return [String, nil] Tool name or nil if not a tool event
81
+ def tool_name
82
+ tool_call&.name
83
+ end
84
+
85
+ # Create a copy of this context with modified attributes
86
+ #
87
+ # Useful for chaining hooks that need to pass modified context
88
+ # to subsequent hooks.
89
+ #
90
+ # @param attributes [Hash] Attributes to override
91
+ # @return [Context] New context with modified attributes
92
+ def with(**attributes)
93
+ Context.new(
94
+ event: attributes[:event] || @event,
95
+ agent_name: attributes[:agent_name] || @agent_name,
96
+ agent_definition: attributes[:agent_definition] || @agent_definition,
97
+ swarm: attributes[:swarm] || @swarm,
98
+ tool_call: attributes[:tool_call] || @tool_call,
99
+ tool_result: attributes[:tool_result] || @tool_result,
100
+ delegation_target: attributes[:delegation_target] || @delegation_target,
101
+ delegation_result: attributes[:delegation_result] || @delegation_result,
102
+ metadata: attributes[:metadata] || @metadata.dup,
103
+ )
104
+ end
105
+
106
+ # Convert to hash for logging and debugging
107
+ #
108
+ # @return [Hash] Simplified hash representation of context
109
+ def to_h
110
+ {
111
+ event: @event,
112
+ agent_name: @agent_name,
113
+ tool_name: tool_name,
114
+ delegation_target: @delegation_target,
115
+ metadata: @metadata,
116
+ }
117
+ end
118
+
119
+ # Convenience methods for creating Results
120
+ # These allow hooks to use `halt("message")` instead of `SwarmSDK::Hooks::Result.halt("message")`
121
+
122
+ # Halt the current operation and return a message
123
+ #
124
+ # @param message [String] Message to return
125
+ # @return [Result] Halt result
126
+ def halt(message)
127
+ Result.halt(message)
128
+ end
129
+
130
+ # Replace the current result with a custom value
131
+ #
132
+ # @param value [Object] Replacement value
133
+ # @return [Result] Replace result
134
+ def replace(value)
135
+ Result.replace(value)
136
+ end
137
+
138
+ # Reprompt the agent with a new prompt
139
+ #
140
+ # @param prompt [String] New prompt
141
+ # @return [Result] Reprompt result
142
+ def reprompt(prompt)
143
+ Result.reprompt(prompt)
144
+ end
145
+
146
+ # Finish the current agent's execution with a final message
147
+ #
148
+ # @param message [String] Final message from the agent
149
+ # @return [Result] Finish agent result
150
+ def finish_agent(message)
151
+ Result.finish_agent(message)
152
+ end
153
+
154
+ # Finish the entire swarm execution with a final message
155
+ #
156
+ # @param message [String] Final message from the swarm
157
+ # @return [Result] Finish swarm result
158
+ def finish_swarm(message)
159
+ Result.finish_swarm(message)
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Hooks
5
+ # Represents a single hook configuration
6
+ #
7
+ # A hook definition includes:
8
+ # - Event type (when to trigger)
9
+ # - Optional matcher (regex for tool names)
10
+ # - Priority (execution order)
11
+ # - Proc to execute
12
+ #
13
+ # @example Create a hook definition
14
+ # definition = SwarmSDK::Hooks::Definition.new(
15
+ # event: :pre_tool_use,
16
+ # matcher: "Write|Edit",
17
+ # priority: 10,
18
+ # proc: ->(ctx) { validate_code(ctx.tool_call) }
19
+ # )
20
+ class Definition
21
+ attr_reader :event, :matcher, :priority, :proc
22
+
23
+ # @param event [Symbol] Event type (e.g., :pre_tool_use)
24
+ # @param matcher [String, Regexp, nil] Optional regex pattern for tool names
25
+ # @param priority [Integer] Execution priority (higher = earlier)
26
+ # @param proc [Proc, Symbol] Hook proc or named hook symbol
27
+ def initialize(event:, matcher: nil, priority: 0, proc:)
28
+ @event = event
29
+ @matcher = compile_matcher(matcher)
30
+ @priority = priority
31
+ @proc = proc
32
+ end
33
+
34
+ # Check if this hook should execute for a given tool name
35
+ #
36
+ # @param tool_name [String] Name of the tool being called
37
+ # @return [Boolean] true if hook should execute
38
+ def matches?(tool_name)
39
+ return true if @matcher.nil? # No matcher = matches everything
40
+
41
+ @matcher.match?(tool_name)
42
+ end
43
+
44
+ # Check if this hook uses a named reference
45
+ #
46
+ # @return [Boolean] true if proc is a symbol (named hook)
47
+ def named_hook?
48
+ @proc.is_a?(Symbol)
49
+ end
50
+
51
+ # Resolve the actual proc, looking up named hooks if needed
52
+ #
53
+ # @param registry [Registry] Registry to lookup named hooks
54
+ # @return [Proc] The actual proc to execute
55
+ # @raise [ArgumentError] if named hook not found
56
+ def resolve_proc(registry)
57
+ return @proc unless named_hook?
58
+
59
+ resolved = registry.get(@proc)
60
+ raise ArgumentError, "Named hook :#{@proc} not found in registry" unless resolved
61
+
62
+ resolved
63
+ end
64
+
65
+ private
66
+
67
+ # Compile matcher string/regexp into regexp
68
+ #
69
+ # @param matcher [String, Regexp, nil] Matcher pattern
70
+ # @return [Regexp, nil] Compiled regex or nil
71
+ def compile_matcher(matcher)
72
+ return if matcher.nil?
73
+ return matcher if matcher.is_a?(Regexp)
74
+
75
+ # Convert string to regex, treating it as a pattern with | for OR
76
+ Regexp.new(matcher)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Hooks
5
+ # Error raised by callbacks to block execution
6
+ #
7
+ # This error provides context about which hook failed and includes
8
+ # the execution context for debugging and error handling.
9
+ #
10
+ # @example Raise a hook error to block execution
11
+ # raise SwarmSDK::Hooks::Error.new(
12
+ # "Validation failed: invalid syntax",
13
+ # hook_name: :validate_code,
14
+ # context: context
15
+ # )
16
+ class Error < StandardError
17
+ attr_reader :hook_name, :context
18
+
19
+ # @param message [String] Error message
20
+ # @param hook_name [Symbol, String, nil] Name of the hook that failed
21
+ # @param context [Context, nil] Execution context when error occurred
22
+ def initialize(message, hook_name: nil, context: nil)
23
+ super(message)
24
+ @hook_name = hook_name
25
+ @context = context
26
+ end
27
+ end
28
+ end
29
+ end