swarm_sdk 2.7.13 → 3.0.0.alpha1

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  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 -273
  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 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
@@ -1,146 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Hooks
5
- # Executes hooks with proper chaining, error handling, and logging
6
- #
7
- # The executor:
8
- # - Chains multiple hooks for the same event
9
- # - Handles errors and blocking (via Error or Result.halt)
10
- # - Respects matcher patterns (only runs matching hooks)
11
- # - Logs execution for debugging
12
- # - Returns Result indicating action to take
13
- #
14
- # @example Execute hooks
15
- # executor = SwarmSDK::Hooks::Executor.new(registry, logger)
16
- # context = SwarmSDK::Hooks::Context.new(...)
17
- # result = executor.execute(event: :pre_tool_use, context: context, hooks: agent_hooks)
18
- # if result.halt?
19
- # # Handle halt
20
- # elsif result.replace?
21
- # # Use replacement value
22
- # end
23
- class Executor
24
- # @param registry [Registry] Hook registry for resolving named hooks
25
- # @param logger [Logger, nil] Logger for debugging (optional)
26
- def initialize(registry, logger: nil)
27
- @registry = registry
28
- @logger = logger || Logger.new(nil) # Null logger if not provided
29
- end
30
-
31
- # Execute all hooks for an event
32
- #
33
- # Execution order:
34
- # 1. Swarm-level defaults (from registry)
35
- # 2. Agent-specific hooks
36
- # 3. Within each group, by priority (highest first)
37
- #
38
- # Hooks must return:
39
- # - Result - to control execution flow (halt, replace, reprompt, continue)
40
- # - nil - treated as continue with unmodified context
41
- #
42
- # @param event [Symbol] Event type
43
- # @param context [Context] Context to pass to hooks
44
- # @param callbacks [Array<Definition>] Agent-specific hooks
45
- # @return [Result] Result indicating action and value
46
- # @raise [Error] If a hook raises an error
47
- def execute(event:, context:, callbacks: [])
48
- # Combine swarm defaults and agent hooks
49
- all_hooks = @registry.get_defaults(event) + callbacks
50
-
51
- # Filter by matcher (for tool events)
52
- if context.tool_event? && context.tool_name
53
- all_hooks = all_hooks.select { |hook| hook.matches?(context.tool_name) }
54
- end
55
-
56
- # Execute hooks in order
57
- all_hooks.each do |hook_def|
58
- result = execute_single(hook_def, context)
59
-
60
- # Only Result controls flow - nil means continue
61
- next unless result.is_a?(Result)
62
-
63
- # Early return for control flow actions
64
- return result if result.halt? || result.replace? || result.reprompt? || result.finish_agent? || result.finish_swarm?
65
-
66
- # Update context if continue with modified context
67
- context = result.value if result.continue? && result.value.is_a?(Context)
68
- end
69
-
70
- # All hooks executed successfully - continue with final context
71
- Result.continue(context)
72
- rescue Error => e
73
- # Re-raise with context for better error messages
74
- @logger.error("Hook blocked execution: #{e.message}")
75
- raise
76
- rescue StandardError => e
77
- # Wrap unexpected errors
78
- @logger.error("Hook failed unexpectedly: #{e.class} - #{e.message}")
79
- @logger.error(e.backtrace.join("\n"))
80
- raise Error.new(
81
- "Hook failed: #{e.message}",
82
- context: context,
83
- )
84
- end
85
-
86
- # Execute a single hook
87
- #
88
- # @param hook_def [Definition] Hook to execute
89
- # @param context [Context] Current context
90
- # @return [Result, nil] Result from hook (Result or nil)
91
- # @raise [Error] If hook execution fails
92
- def execute_single(hook_def, context)
93
- proc = hook_def.resolve_proc(@registry)
94
-
95
- @logger.debug("Executing hook for #{context.event} (agent: #{context.agent_name})")
96
-
97
- # Execute hook with context as parameter
98
- # Users can access convenience methods via context parameter:
99
- # hook(:event) { |ctx| ctx.halt("msg") }
100
- # This preserves lexical scope and access to surrounding instance variables
101
- proc.call(context)
102
- rescue Error
103
- # Pass through blocking errors
104
- raise
105
- rescue StandardError => e
106
- # Wrap other errors with context and detailed debugging
107
- hook_name = hook_def.named_hook? ? hook_def.proc : "anonymous"
108
-
109
- # Log detailed error info for debugging
110
- @logger.error("=" * 80)
111
- @logger.error("HOOK EXECUTION ERROR")
112
- @logger.error(" Hook: #{hook_name}")
113
- @logger.error(" Event: #{context.event}")
114
- @logger.error(" Agent: #{context.agent_name}")
115
- @logger.error(" Proc class: #{proc.class}")
116
- @logger.error(" Proc arity: #{proc.arity} (expected: 1 for |context|)")
117
- @logger.error(" Error: #{e.class.name}: #{e.message}")
118
- @logger.error(" Backtrace:")
119
- e.backtrace.first(15).each { |line| @logger.error(" #{line}") }
120
- @logger.error("=" * 80)
121
-
122
- raise Error.new(
123
- "Hook #{hook_name} failed: #{e.message}",
124
- hook_name: hook_name,
125
- context: context,
126
- )
127
- end
128
-
129
- # Execute hooks and return result safely (without raising)
130
- #
131
- # This is a convenience method that catches Error and converts it
132
- # to a halt result, making it easier to use in control flow.
133
- #
134
- # @param event [Symbol] Event type
135
- # @param context [Context] Context to pass to hooks
136
- # @param callbacks [Array<Definition>] Agent-specific hooks
137
- # @return [Result] Result from hooks
138
- def execute_safe(event:, context:, callbacks: [])
139
- execute(event: event, context: context, callbacks: callbacks)
140
- rescue Error => e
141
- @logger.warn("Execution blocked by hook: #{e.message}")
142
- Result.halt(e.message)
143
- end
144
- end
145
- end
146
- end
@@ -1,147 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Hooks
5
- # Central registry for managing named hooks and swarm-level defaults
6
- #
7
- # The Registry stores:
8
- # - Named hooks that can be referenced by symbol in YAML or code
9
- # - Swarm-level default hooks that apply to all agents
10
- #
11
- # @example Register a named hook
12
- # registry = SwarmSDK::Hooks::Registry.new
13
- # registry.register(:validate_code) do |context|
14
- # raise SwarmSDK::Hooks::Error, "Invalid code" unless valid?(context.tool_call)
15
- # end
16
- #
17
- # @example Add swarm-level default
18
- # registry.add_default(:pre_tool_use, matcher: "Write|Edit") do |context|
19
- # puts "Tool #{context.tool_call.name} called by #{context.agent_name}"
20
- # end
21
- class Registry
22
- # Available hook event types
23
- VALID_EVENTS = [
24
- # Swarm lifecycle events
25
- :swarm_start, # When Swarm.execute is called (before first user message)
26
- :swarm_stop, # When Swarm.execute completes (after execution)
27
- :first_message, # When first user message is sent to swarm (Swarm.execute)
28
-
29
- # Agent/LLM events
30
- :user_prompt, # Before sending user message to LLM
31
- :agent_step, # After agent makes intermediate response with tool calls
32
- :agent_stop, # After agent completes with final response (no more tool calls)
33
-
34
- # Tool events
35
- :pre_tool_use, # Before tool execution (can block/modify)
36
- :post_tool_use, # After tool execution
37
-
38
- # Delegation events
39
- :pre_delegation, # Before delegating to another agent
40
- :post_delegation, # After delegation completes
41
-
42
- # Context events
43
- :context_warning, # When context usage crosses threshold
44
-
45
- # Debug events
46
- :breakpoint_enter, # When entering interactive debugging (binding.irb)
47
- :breakpoint_exit, # When exiting interactive debugging
48
- ].freeze
49
-
50
- def initialize
51
- @named_hooks = {} # { hook_name: proc }
52
- @defaults = Hash.new { |h, k| h[k] = [] } # { event_type: [Definition, ...] }
53
- end
54
-
55
- # Register a named hook that can be referenced elsewhere
56
- #
57
- # @param name [Symbol] Unique name for this hook
58
- # @param block [Proc] Hook implementation
59
- # @raise [ArgumentError] if name already registered or invalid
60
- #
61
- # @example
62
- # registry.register(:log_tool_use) { |ctx| puts "Tool: #{ctx.tool_call.name}" }
63
- def register(name, &block)
64
- raise ArgumentError, "Hook name must be a symbol" unless name.is_a?(Symbol)
65
- raise ArgumentError, "Hook #{name} already registered" if @named_hooks.key?(name)
66
- raise ArgumentError, "Block required" unless block
67
-
68
- @named_hooks[name] = block
69
- end
70
-
71
- # Get a named hook by symbol
72
- #
73
- # @param name [Symbol] Hook name
74
- # @return [Proc, nil] The hook proc or nil if not found
75
- def get(name)
76
- @named_hooks[name]
77
- end
78
-
79
- # Add a swarm-level default hook
80
- #
81
- # These hooks apply to all agents unless overridden at agent level.
82
- #
83
- # @param event [Symbol] Event type (must be in VALID_EVENTS)
84
- # @param matcher [String, Regexp, nil] Optional regex pattern to match tool names
85
- # @param priority [Integer] Execution priority (higher runs first)
86
- # @param block [Proc] Hook implementation
87
- # @raise [ArgumentError] if event invalid or block missing
88
- #
89
- # @example Add default logging
90
- # registry.add_default(:pre_tool_use) { |ctx| log(ctx) }
91
- #
92
- # @example Add validation for specific tools
93
- # registry.add_default(:pre_tool_use, matcher: "Write|Edit") do |ctx|
94
- # validate_tool_call(ctx.tool_call)
95
- # end
96
- def add_default(event, matcher: nil, priority: 0, &block)
97
- validate_event!(event)
98
- raise ArgumentError, "Block required" unless block
99
-
100
- definition = Definition.new(
101
- event: event,
102
- matcher: matcher,
103
- priority: priority,
104
- proc: block,
105
- )
106
-
107
- @defaults[event] << definition
108
- @defaults[event].sort_by! { |d| -d.priority } # Higher priority first
109
- end
110
-
111
- # Get all default hooks for an event type
112
- #
113
- # @param event [Symbol] Event type
114
- # @return [Array<Definition>] List of hook definitions
115
- def get_defaults(event)
116
- @defaults[event]
117
- end
118
-
119
- # Get all registered named hook names
120
- #
121
- # @return [Array<Symbol>] List of hook names
122
- def named_hooks
123
- @named_hooks.keys
124
- end
125
-
126
- # Check if a hook name is registered
127
- #
128
- # @param name [Symbol] Hook name
129
- # @return [Boolean] true if registered
130
- def registered?(name)
131
- @named_hooks.key?(name)
132
- end
133
-
134
- private
135
-
136
- # Validate event type
137
- #
138
- # @param event [Symbol] Event to validate
139
- # @raise [ArgumentError] if event invalid
140
- def validate_event!(event)
141
- return if VALID_EVENTS.include?(event)
142
-
143
- raise ArgumentError, "Invalid event type: #{event}. Valid types: #{VALID_EVENTS.join(", ")}"
144
- end
145
- end
146
- end
147
- end
@@ -1,150 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Hooks
5
- # Result object returned by hooks to control execution flow
6
- #
7
- # Hooks can return a Result to:
8
- # - Continue normal execution (default)
9
- # - Halt execution and return an error/message
10
- # - Replace a value (e.g., tool result, delegation result)
11
- # - Reprompt the agent with a new prompt
12
- # - Finish the current agent's execution (agent scope)
13
- # - Finish the entire swarm execution (swarm scope)
14
- #
15
- # @example Continue normal execution (default)
16
- # SwarmSDK::Hooks::Result.continue
17
- #
18
- # @example Halt execution with error message
19
- # SwarmSDK::Hooks::Result.halt("Validation failed: invalid input")
20
- #
21
- # @example Replace tool result
22
- # SwarmSDK::Hooks::Result.replace("Custom tool result")
23
- #
24
- # @example Reprompt agent with modified prompt
25
- # SwarmSDK::Hooks::Result.reprompt("Modified task: #{original_task}")
26
- #
27
- # @example Finish current agent with message
28
- # SwarmSDK::Hooks::Result.finish_agent("Agent task completed")
29
- #
30
- # @example Finish entire swarm with message
31
- # SwarmSDK::Hooks::Result.finish_swarm("All tasks complete!")
32
- class Result
33
- attr_reader :action, :value
34
-
35
- # Valid actions that control execution flow
36
- VALID_ACTIONS = [:continue, :halt, :replace, :reprompt, :finish_agent, :finish_swarm].freeze
37
-
38
- # @param action [Symbol] Action to take (:continue, :halt, :replace, :reprompt)
39
- # @param value [Object, nil] Associated value (context for :continue, message for :halt, etc.)
40
- def initialize(action:, value: nil)
41
- unless VALID_ACTIONS.include?(action)
42
- raise ArgumentError, "Invalid action: #{action}. Valid actions: #{VALID_ACTIONS.join(", ")}"
43
- end
44
-
45
- @action = action
46
- @value = value
47
- end
48
-
49
- # Check if this result indicates halting execution
50
- #
51
- # @return [Boolean] true if action is :halt
52
- def halt?
53
- @action == :halt
54
- end
55
-
56
- # Check if this result provides a replacement value
57
- #
58
- # @return [Boolean] true if action is :replace
59
- def replace?
60
- @action == :replace
61
- end
62
-
63
- # Check if this result requests reprompting
64
- #
65
- # @return [Boolean] true if action is :reprompt
66
- def reprompt?
67
- @action == :reprompt
68
- end
69
-
70
- # Check if this result continues normal execution
71
- #
72
- # @return [Boolean] true if action is :continue
73
- def continue?
74
- @action == :continue
75
- end
76
-
77
- # Check if this result finishes the current agent
78
- #
79
- # @return [Boolean] true if action is :finish_agent
80
- def finish_agent?
81
- @action == :finish_agent
82
- end
83
-
84
- # Check if this result finishes the entire swarm
85
- #
86
- # @return [Boolean] true if action is :finish_swarm
87
- def finish_swarm?
88
- @action == :finish_swarm
89
- end
90
-
91
- class << self
92
- # Create a result that continues normal execution
93
- #
94
- # @param context [Context, nil] Updated context (optional)
95
- # @return [Result] Result with continue action
96
- def continue(context = nil)
97
- new(action: :continue, value: context)
98
- end
99
-
100
- # Create a result that halts execution
101
- #
102
- # @param message [String] Error or halt message
103
- # @return [Result] Result with halt action
104
- def halt(message)
105
- new(action: :halt, value: message)
106
- end
107
-
108
- # Create a result that replaces a value
109
- #
110
- # @param value [Object] Replacement value
111
- # @return [Result] Result with replace action
112
- def replace(value)
113
- new(action: :replace, value: value)
114
- end
115
-
116
- # Create a result that reprompts the agent
117
- #
118
- # @param prompt [String] New prompt to send to agent
119
- # @return [Result] Result with reprompt action
120
- def reprompt(prompt)
121
- new(action: :reprompt, value: prompt)
122
- end
123
-
124
- # Create a result that finishes the current agent
125
- #
126
- # Exits the agent's chat loop and returns the message as the agent's
127
- # final response. If this agent was delegated to, control returns to
128
- # the calling agent. The swarm continues if there's more work.
129
- #
130
- # @param message [String] Final message from the agent
131
- # @return [Result] Result with finish_agent action
132
- def finish_agent(message)
133
- new(action: :finish_agent, value: message)
134
- end
135
-
136
- # Create a result that finishes the entire swarm
137
- #
138
- # Immediately exits the Swarm.execute() loop and returns the message
139
- # as the final Result#output. All agent execution stops and the user
140
- # receives this message.
141
- #
142
- # @param message [String] Final message from the swarm
143
- # @return [Result] Result with finish_swarm action
144
- def finish_swarm(message)
145
- new(action: :finish_swarm, value: message)
146
- end
147
- end
148
- end
149
- end
150
- end
@@ -1,256 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "open3"
4
- require "json"
5
- require "timeout"
6
-
7
- module SwarmSDK
8
- module Hooks
9
- # Executes shell command hooks with JSON I/O and exit code handling
10
- #
11
- # ShellExecutor runs external shell commands (defined in YAML hooks) and
12
- # converts their exit codes to Result objects that control execution flow.
13
- #
14
- # ## Exit Code Behavior (following Claude Code convention)
15
- #
16
- # - **0**: Success - continue execution (Result.continue)
17
- # - **2**: Block with error feedback to LLM (Result.halt)
18
- # - **Other**: Non-blocking error - log warning and continue (Result.continue)
19
- #
20
- # ## JSON I/O Format
21
- #
22
- # **stdin (to hook script)**:
23
- # ```json
24
- # {
25
- # "event": "pre_tool_use",
26
- # "agent": "backend",
27
- # "tool": "Write",
28
- # "parameters": { "file_path": "api.rb", "content": "..." }
29
- # }
30
- # ```
31
- #
32
- # **stdout (from hook script)**:
33
- # ```json
34
- # {
35
- # "success": false,
36
- # "error": "Validation failed: syntax error"
37
- # }
38
- # ```
39
- #
40
- # @example Execute a validation hook
41
- # result = SwarmSDK::Hooks::ShellExecutor.execute(
42
- # command: "python scripts/validate.py",
43
- # input_json: { event: "pre_tool_use", tool: "Write", parameters: {...} },
44
- # timeout: 10,
45
- # agent_name: :backend,
46
- # swarm_name: "Dev Team"
47
- # )
48
- # # => Result (continue or halt based on exit code)
49
- class ShellExecutor
50
- # NOTE: Timeout now accessed via SwarmSDK.config.hook_shell_timeout
51
-
52
- class << self
53
- # Execute a shell command hook
54
- #
55
- # @param command [String] Shell command to execute
56
- # @param input_json [Hash] JSON data to provide on stdin
57
- # @param timeout [Integer] Timeout in seconds (default: 60)
58
- # @param agent_name [Symbol, String, nil] Agent name for environment variables
59
- # @param swarm_name [String, nil] Swarm name for environment variables
60
- # @param event [Symbol] Event type for context-aware behavior
61
- # @return [Result] Result based on exit code (continue or halt)
62
- def execute(command:, input_json:, timeout: nil, agent_name: nil, swarm_name: nil, event: nil)
63
- timeout ||= SwarmSDK.config.hook_shell_timeout
64
-
65
- # Build environment variables
66
- env = build_environment(agent_name: agent_name, swarm_name: swarm_name)
67
-
68
- # Execute command with JSON stdin and timeout
69
- stdout, stderr, status = Timeout.timeout(timeout) do
70
- Open3.capture3(
71
- env,
72
- command,
73
- stdin_data: JSON.generate(input_json),
74
- )
75
- end
76
-
77
- # Handle exit code per Claude Code convention (context-aware)
78
- result = handle_exit_code(status.exitstatus, stdout, stderr, event)
79
-
80
- # Emit log event for hook execution
81
- case status.exitstatus
82
- when 0
83
- # Success - log stdout/stderr
84
- emit_hook_log(
85
- event: event,
86
- agent_name: agent_name,
87
- command: command,
88
- exit_code: status.exitstatus,
89
- success: true,
90
- stdout: stdout,
91
- stderr: stderr,
92
- )
93
- when 2
94
- # Blocking error - always log stderr
95
- emit_hook_log(
96
- event: event,
97
- agent_name: agent_name,
98
- command: command,
99
- exit_code: status.exitstatus,
100
- success: false,
101
- stderr: stderr,
102
- blocked: true,
103
- )
104
- else
105
- # Non-blocking error - log stderr
106
- emit_hook_log(
107
- event: event,
108
- agent_name: agent_name,
109
- command: command,
110
- exit_code: status.exitstatus,
111
- success: false,
112
- stderr: stderr,
113
- blocked: false,
114
- )
115
- end
116
-
117
- result
118
- rescue Timeout::Error
119
- emit_hook_log(
120
- event: event,
121
- agent_name: agent_name,
122
- command: command,
123
- exit_code: nil,
124
- success: false,
125
- stderr: "Timeout after #{timeout}s",
126
- )
127
- # Don't block on timeout - log and continue
128
- Result.continue
129
- rescue StandardError => e
130
- emit_hook_log(
131
- event: event,
132
- agent_name: agent_name,
133
- command: command,
134
- exit_code: nil,
135
- success: false,
136
- stderr: e.message,
137
- )
138
- # Don't block on errors - log and continue
139
- Result.continue
140
- end
141
-
142
- private
143
-
144
- # Build environment variables for hook execution
145
- #
146
- # @param agent_name [Symbol, String, nil] Agent name
147
- # @param swarm_name [String, nil] Swarm name
148
- # @return [Hash] Environment variables
149
- def build_environment(agent_name:, swarm_name:)
150
- {
151
- "SWARM_SDK_PROJECT_DIR" => Dir.pwd,
152
- "SWARM_SDK_AGENT_NAME" => agent_name.to_s,
153
- "SWARM_SDK_SWARM_NAME" => swarm_name.to_s,
154
- "PATH" => ENV.fetch("PATH", ""),
155
- }
156
- end
157
-
158
- # Handle exit code and return appropriate Result
159
- #
160
- # @param exit_code [Integer] Process exit code
161
- # @param stdout [String] Standard output
162
- # @param stderr [String] Standard error
163
- # @param event [Symbol] Hook event type
164
- # @return [Result] Result based on exit code
165
- def handle_exit_code(exit_code, stdout, stderr, event)
166
- case exit_code
167
- when 0
168
- # Success - continue execution
169
- # For user_prompt and swarm_start: return stdout to be shown to agent
170
- if [:user_prompt, :swarm_start].include?(event) && !stdout.strip.empty?
171
- # Return Result with stdout that will be appended to prompt
172
- Result.replace(stdout.strip)
173
- else
174
- # Normal success
175
- Result.continue
176
- end
177
- when 2
178
- # Blocking error - behavior depends on event type
179
- handle_exit_code_2(event, stdout, stderr)
180
- else
181
- # Non-blocking error - continue (stderr logged above)
182
- Result.continue
183
- end
184
- end
185
-
186
- # Handle exit code 2 (blocking error)
187
- #
188
- # @param event [Symbol] Hook event type
189
- # @param _stdout [String] Standard output (unused)
190
- # @param stderr [String] Standard error
191
- # @return [Result] Result based on event type
192
- def handle_exit_code_2(event, _stdout, stderr)
193
- error_msg = stderr.strip
194
-
195
- case event
196
- when :pre_tool_use
197
- # Block tool call, show stderr to agent (stderr already logged above)
198
- Result.halt(error_msg)
199
- when :post_tool_use
200
- # Tool already ran, show stderr to agent (stderr already logged above)
201
- Result.halt(error_msg)
202
- when :user_prompt
203
- # Block prompt processing, erase prompt
204
- # stderr is logged (above) for user to see, but NOT shown to agent
205
- # Return empty halt (prompt is erased, no message to agent)
206
- Result.halt("")
207
- when :agent_stop
208
- # Block stoppage, show stderr to agent (stderr already logged above)
209
- Result.halt(error_msg)
210
- when :context_warning, :swarm_start, :swarm_stop
211
- # N/A - stderr logged above, don't halt
212
- Result.continue
213
- else
214
- # Default: halt with stderr
215
- Result.halt(error_msg)
216
- end
217
- end
218
-
219
- # Emit hook execution log entry
220
- #
221
- # @param event [Symbol] Hook event type
222
- # @param agent_name [String, Symbol, nil] Agent name
223
- # @param command [String] Shell command executed
224
- # @param exit_code [Integer, nil] Process exit code
225
- # @param success [Boolean] Whether execution succeeded
226
- # @param stdout [String, nil] Standard output
227
- # @param stderr [String, nil] Standard error
228
- # @param blocked [Boolean] Whether execution was blocked (exit code 2)
229
- def emit_hook_log(event:, agent_name:, command:, exit_code:, success:, stdout: nil, stderr: nil, blocked: false)
230
- # Only emit if LogStream is enabled (has emitter)
231
- return unless LogStream.enabled?
232
-
233
- log_entry = {
234
- type: "hook_executed",
235
- hook_event: event&.to_s, # Which hook event triggered this (pre_tool_use, swarm_start, etc.)
236
- agent: agent_name,
237
- command: command,
238
- exit_code: exit_code,
239
- success: success,
240
- }
241
-
242
- # Add stdout if present (exit code 0)
243
- log_entry[:stdout] = stdout.strip if stdout && !stdout.strip.empty?
244
-
245
- # Add stderr if present (any exit code)
246
- log_entry[:stderr] = stderr.strip if stderr && !stderr.strip.empty?
247
-
248
- # Add blocked flag for exit code 2
249
- log_entry[:blocked] = true if blocked
250
-
251
- LogStream.emit(**log_entry)
252
- end
253
- end
254
- end
255
- end
256
- end