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.
- checksums.yaml +7 -0
- data/lib/swarm_sdk/agent/builder.rb +333 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +271 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +99 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +114 -0
- data/lib/swarm_sdk/agent/chat.rb +779 -0
- data/lib/swarm_sdk/agent/context.rb +108 -0
- data/lib/swarm_sdk/agent/definition.rb +335 -0
- data/lib/swarm_sdk/configuration.rb +251 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +163 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +143 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +83 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +46 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +237 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +224 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +62 -0
- data/lib/swarm_sdk/swarm/builder.rb +240 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +267 -0
- data/lib/swarm_sdk/swarm.rb +837 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/delegate.rb +152 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +231 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +73 -0
- data/lib/swarm_sdk/tools/scratchpad_list.rb +88 -0
- data/lib/swarm_sdk/tools/scratchpad_read.rb +59 -0
- data/lib/swarm_sdk/tools/scratchpad_write.rb +88 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad.rb +153 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +69 -0
- 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
|