swarm_sdk 2.7.14 → 3.0.0.alpha2

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 (185) 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/document_converters/base.rb +84 -0
  42. data/lib/swarm_sdk/v3/tools/document_converters/docx_converter.rb +120 -0
  43. data/lib/swarm_sdk/v3/tools/document_converters/pdf_converter.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/document_converters/xlsx_converter.rb +128 -0
  45. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  46. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  47. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  48. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  49. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  50. data/lib/swarm_sdk/v3/tools/read.rb +213 -0
  51. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  52. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  53. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  54. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  55. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  56. data/lib/swarm_sdk/v3.rb +145 -0
  57. metadata +88 -149
  58. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  59. data/lib/swarm_sdk/agent/builder.rb +0 -705
  60. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  61. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  62. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  63. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  64. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  65. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  66. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  67. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  68. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  69. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  70. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  71. data/lib/swarm_sdk/agent/context.rb +0 -115
  72. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  73. data/lib/swarm_sdk/agent/definition.rb +0 -588
  74. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  75. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  76. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  77. data/lib/swarm_sdk/agent_registry.rb +0 -146
  78. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  79. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  80. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  81. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  82. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  83. data/lib/swarm_sdk/config.rb +0 -368
  84. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  85. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  86. data/lib/swarm_sdk/configuration.rb +0 -165
  87. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  88. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  89. data/lib/swarm_sdk/context_compactor.rb +0 -335
  90. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  91. data/lib/swarm_sdk/context_management/context.rb +0 -328
  92. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  93. data/lib/swarm_sdk/defaults.rb +0 -251
  94. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  95. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  96. data/lib/swarm_sdk/hooks/context.rb +0 -197
  97. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  98. data/lib/swarm_sdk/hooks/error.rb +0 -29
  99. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  100. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  101. data/lib/swarm_sdk/hooks/result.rb +0 -150
  102. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  103. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  104. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  105. data/lib/swarm_sdk/log_collector.rb +0 -227
  106. data/lib/swarm_sdk/log_stream.rb +0 -127
  107. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  108. data/lib/swarm_sdk/model_aliases.json +0 -8
  109. data/lib/swarm_sdk/models.json +0 -44002
  110. data/lib/swarm_sdk/models.rb +0 -161
  111. data/lib/swarm_sdk/node_context.rb +0 -245
  112. data/lib/swarm_sdk/observer/builder.rb +0 -81
  113. data/lib/swarm_sdk/observer/config.rb +0 -45
  114. data/lib/swarm_sdk/observer/manager.rb +0 -248
  115. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  116. data/lib/swarm_sdk/permissions/config.rb +0 -239
  117. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  118. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  119. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  120. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  121. data/lib/swarm_sdk/plugin.rb +0 -309
  122. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  123. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  124. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  125. data/lib/swarm_sdk/restore_result.rb +0 -65
  126. data/lib/swarm_sdk/result.rb +0 -241
  127. data/lib/swarm_sdk/snapshot.rb +0 -156
  128. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  129. data/lib/swarm_sdk/state_restorer.rb +0 -476
  130. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  131. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  132. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  133. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  134. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  135. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  136. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  141. data/lib/swarm_sdk/swarm.rb +0 -973
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/base.rb +0 -63
  145. data/lib/swarm_sdk/tools/bash.rb +0 -280
  146. data/lib/swarm_sdk/tools/clock.rb +0 -46
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  160. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  161. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  162. data/lib/swarm_sdk/tools/read.rb +0 -261
  163. data/lib/swarm_sdk/tools/registry.rb +0 -205
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  166. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  167. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  168. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  169. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  170. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  171. data/lib/swarm_sdk/tools/think.rb +0 -100
  172. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  173. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  174. data/lib/swarm_sdk/tools/write.rb +0 -112
  175. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  176. data/lib/swarm_sdk/utils.rb +0 -68
  177. data/lib/swarm_sdk/validation_result.rb +0 -33
  178. data/lib/swarm_sdk/version.rb +0 -5
  179. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  180. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  181. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  182. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  183. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  184. data/lib/swarm_sdk/workflow.rb +0 -589
  185. 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