swarm_sdk 2.7.14 → 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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
  4. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  5. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  6. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  7. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  8. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  9. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  10. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  11. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  12. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  13. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  14. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  15. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  16. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  17. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  18. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  19. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  20. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  24. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  25. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  26. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  27. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  28. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  29. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  30. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  31. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  32. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  33. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  34. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  35. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  36. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  37. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  38. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  39. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  40. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  41. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  42. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  43. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  44. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  45. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  46. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  47. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  48. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  49. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  50. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  51. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  52. data/lib/swarm_sdk/v3.rb +145 -0
  53. metadata +83 -148
  54. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  55. data/lib/swarm_sdk/agent/builder.rb +0 -705
  56. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  57. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  58. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  59. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  60. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  61. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  62. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  63. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  64. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  65. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  66. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  67. data/lib/swarm_sdk/agent/context.rb +0 -115
  68. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  69. data/lib/swarm_sdk/agent/definition.rb +0 -588
  70. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  71. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  72. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  73. data/lib/swarm_sdk/agent_registry.rb +0 -146
  74. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  75. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  76. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  77. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  78. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  79. data/lib/swarm_sdk/config.rb +0 -368
  80. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  81. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  82. data/lib/swarm_sdk/configuration.rb +0 -165
  83. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  84. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  85. data/lib/swarm_sdk/context_compactor.rb +0 -335
  86. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  87. data/lib/swarm_sdk/context_management/context.rb +0 -328
  88. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  89. data/lib/swarm_sdk/defaults.rb +0 -251
  90. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  91. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  92. data/lib/swarm_sdk/hooks/context.rb +0 -197
  93. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  94. data/lib/swarm_sdk/hooks/error.rb +0 -29
  95. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  96. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  97. data/lib/swarm_sdk/hooks/result.rb +0 -150
  98. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  99. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  100. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  101. data/lib/swarm_sdk/log_collector.rb +0 -227
  102. data/lib/swarm_sdk/log_stream.rb +0 -127
  103. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  104. data/lib/swarm_sdk/model_aliases.json +0 -8
  105. data/lib/swarm_sdk/models.json +0 -44002
  106. data/lib/swarm_sdk/models.rb +0 -161
  107. data/lib/swarm_sdk/node_context.rb +0 -245
  108. data/lib/swarm_sdk/observer/builder.rb +0 -81
  109. data/lib/swarm_sdk/observer/config.rb +0 -45
  110. data/lib/swarm_sdk/observer/manager.rb +0 -248
  111. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  112. data/lib/swarm_sdk/permissions/config.rb +0 -239
  113. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  114. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  115. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  116. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  117. data/lib/swarm_sdk/plugin.rb +0 -309
  118. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  119. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  120. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  121. data/lib/swarm_sdk/restore_result.rb +0 -65
  122. data/lib/swarm_sdk/result.rb +0 -241
  123. data/lib/swarm_sdk/snapshot.rb +0 -156
  124. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  125. data/lib/swarm_sdk/state_restorer.rb +0 -476
  126. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  127. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  128. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  129. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  130. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  131. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  132. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  133. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  134. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  135. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  136. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  137. data/lib/swarm_sdk/swarm.rb +0 -973
  138. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  139. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  140. data/lib/swarm_sdk/tools/base.rb +0 -63
  141. data/lib/swarm_sdk/tools/bash.rb +0 -280
  142. data/lib/swarm_sdk/tools/clock.rb +0 -46
  143. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  144. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  145. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  146. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  147. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  148. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  149. data/lib/swarm_sdk/tools/edit.rb +0 -145
  150. data/lib/swarm_sdk/tools/glob.rb +0 -166
  151. data/lib/swarm_sdk/tools/grep.rb +0 -235
  152. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  153. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  154. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  155. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  156. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  157. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  158. data/lib/swarm_sdk/tools/read.rb +0 -261
  159. data/lib/swarm_sdk/tools/registry.rb +0 -205
  160. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  161. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  163. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  164. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  165. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  166. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  167. data/lib/swarm_sdk/tools/think.rb +0 -100
  168. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  169. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  170. data/lib/swarm_sdk/tools/write.rb +0 -112
  171. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  172. data/lib/swarm_sdk/utils.rb +0 -68
  173. data/lib/swarm_sdk/validation_result.rb +0 -33
  174. data/lib/swarm_sdk/version.rb +0 -5
  175. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  176. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  177. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  178. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  179. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  180. data/lib/swarm_sdk/workflow.rb +0 -589
  181. data/lib/swarm_sdk.rb +0 -721
@@ -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