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,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- class Swarm
5
- # Builder for swarm registry in DSL
6
- #
7
- # Supports registering external swarms for composable swarms pattern.
8
- #
9
- # @example
10
- # swarms do
11
- # register "code_review", file: "./swarms/code_review.rb"
12
- # register "testing", file: "./swarms/testing.yml", keep_context: false
13
- # end
14
- #
15
- # @example Inline swarm definition
16
- # swarms do
17
- # register "tester" do
18
- # lead :tester
19
- # agent :tester do
20
- # model "gpt-4o-mini"
21
- # system "You test code"
22
- # end
23
- # end
24
- # end
25
- #
26
- class SwarmRegistryBuilder
27
- attr_reader :registrations
28
-
29
- def initialize
30
- @registrations = []
31
- end
32
-
33
- # Register a swarm from file, YAML string, or inline block
34
- #
35
- # @param name [String, Symbol] Registration name
36
- # @param file [String, nil] Path to swarm file (.rb or .yml)
37
- # @param yaml [String, nil] YAML content string
38
- # @param keep_context [Boolean] Whether to preserve conversation state (default: true)
39
- # @yield Optional block for inline swarm definition
40
- # @raise [ArgumentError] If neither file, yaml, nor block provided
41
- def register(name, file: nil, yaml: nil, keep_context: true, &block)
42
- # Validate that exactly one source is provided
43
- sources = [file, yaml, block].compact
44
- if sources.empty?
45
- raise ArgumentError, "register '#{name}' requires either file:, yaml:, or a block"
46
- elsif sources.size > 1
47
- raise ArgumentError, "register '#{name}' accepts only one of: file:, yaml:, or block (got #{sources.size})"
48
- end
49
-
50
- # Determine source type and store
51
- source = if file
52
- { type: :file, value: file }
53
- elsif yaml
54
- { type: :yaml, value: yaml }
55
- else
56
- { type: :block, value: block }
57
- end
58
-
59
- @registrations << {
60
- name: name.to_s,
61
- source: source,
62
- keep_context: keep_context,
63
- }
64
- end
65
- end
66
- end
67
- end
@@ -1,392 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- class Swarm
5
- # Handles tool creation, registration, and permissions wrapping
6
- #
7
- # Responsibilities:
8
- # - Register explicit tools for agents
9
- # - Register default tools (Read, Grep, Glob, etc.)
10
- # - Create tool instances (with agent context)
11
- # - Wrap tools with permissions validators
12
- #
13
- # This encapsulates all tool-related logic that was previously in Swarm.
14
- class ToolConfigurator
15
- # Default tools available to all agents (unless disable_default_tools is set)
16
- DEFAULT_TOOLS = [
17
- :Read,
18
- :Grep,
19
- :Glob,
20
- ].freeze
21
-
22
- # Scratchpad tools (added if scratchpad is enabled)
23
- SCRATCHPAD_TOOLS = [
24
- :ScratchpadWrite,
25
- :ScratchpadRead,
26
- :ScratchpadList,
27
- ].freeze
28
-
29
- # Filesystem tools that can be globally disabled for security
30
- FILESYSTEM_TOOLS = [
31
- :Read,
32
- :Write,
33
- :Edit,
34
- :MultiEdit,
35
- :Grep,
36
- :Glob,
37
- :Bash,
38
- ].freeze
39
-
40
- def initialize(swarm, scratchpad_storage, plugin_storages = {})
41
- @swarm = swarm
42
- @scratchpad_storage = scratchpad_storage
43
- # Plugin storages: { plugin_name => { agent_name => storage } }
44
- # e.g., { memory: { agent1: storage1, agent2: storage2 } }
45
- @plugin_storages = plugin_storages
46
- end
47
-
48
- # Register all tools for an agent (both explicit and default)
49
- #
50
- # @param chat [AgentChat] The chat instance to register tools with
51
- # @param agent_name [Symbol] Name of the agent
52
- # @param agent_definition [AgentDefinition] Agent definition object
53
- def register_all_tools(chat:, agent_name:, agent_definition:)
54
- register_explicit_tools(chat, agent_definition.tools, agent_name: agent_name, agent_definition: agent_definition)
55
- register_default_tools(chat, agent_name: agent_name, agent_definition: agent_definition)
56
- end
57
-
58
- # Create a tool instance by name
59
- #
60
- # Uses the Registry factory pattern to instantiate tools based on their
61
- # declared requirements. This eliminates the need for a giant case statement.
62
- #
63
- # Tool lookup order:
64
- # 1. Plugin tools (registered via SwarmSDK::PluginRegistry)
65
- # 2. Custom tools (registered via SwarmSDK.register_tool)
66
- # 3. Built-in tools (SwarmSDK::Tools::Registry)
67
- #
68
- # File tools and TodoWrite require agent context for tracking state.
69
- # Scratchpad tools require shared scratchpad instance.
70
- # Plugin tools are delegated to their respective plugins.
71
- #
72
- # This method is public for testing delegation from Swarm.
73
- #
74
- # @param tool_name [Symbol, String] Tool name
75
- # @param agent_name [Symbol] Agent name for context
76
- # @param directory [String] Agent's working directory
77
- # @param chat [Agent::Chat, nil] Optional chat instance for tools that need it
78
- # @param agent_definition [Agent::Definition, nil] Optional agent definition
79
- # @return [RubyLLM::Tool] Tool instance
80
- def create_tool_instance(tool_name, agent_name, directory, chat: nil, agent_definition: nil)
81
- tool_name_sym = tool_name.to_sym
82
-
83
- # Check if tool is provided by a plugin
84
- if PluginRegistry.plugin_tool?(tool_name_sym)
85
- return create_plugin_tool(tool_name_sym, agent_name, directory, chat, agent_definition)
86
- end
87
-
88
- # Check if tool is a custom registered tool
89
- if CustomToolRegistry.registered?(tool_name_sym)
90
- context = {
91
- agent_name: agent_name,
92
- directory: directory,
93
- }
94
- return CustomToolRegistry.create(tool_name_sym, context)
95
- end
96
-
97
- # Use Registry factory pattern for built-in tools
98
- context = {
99
- agent_name: agent_name,
100
- directory: directory,
101
- scratchpad_storage: @scratchpad_storage,
102
- }
103
-
104
- Tools::Registry.create(tool_name_sym, context)
105
- end
106
-
107
- # Wrap a tool instance with permissions validator if configured
108
- #
109
- # This method is public for testing delegation from Swarm.
110
- #
111
- # @param tool_instance [RubyLLM::Tool] Tool instance to wrap
112
- # @param permissions_config [Hash, nil] Permission configuration
113
- # @param agent_definition [AgentDefinition] Agent definition
114
- # @return [RubyLLM::Tool] Either the wrapped tool or original tool
115
- def wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
116
- # Skip wrapping if no permissions or agent bypasses permissions
117
- return tool_instance unless permissions_config
118
- return tool_instance if agent_definition.bypass_permissions
119
-
120
- # Create permissions config and wrap tool with validator
121
- permissions = Permissions::Config.new(
122
- permissions_config,
123
- base_directory: agent_definition.directory,
124
- )
125
-
126
- Permissions::Validator.new(tool_instance, permissions)
127
- end
128
-
129
- private
130
-
131
- # Register explicitly configured tools
132
- #
133
- # @param chat [AgentChat] The chat instance
134
- # @param tool_configs [Array<Hash>] Tool configurations with optional permissions
135
- # @param agent_name [Symbol] Agent name
136
- # @param agent_definition [AgentDefinition] Agent definition
137
- def register_explicit_tools(chat, tool_configs, agent_name:, agent_definition:)
138
- # Validate filesystem tools if globally disabled
139
- unless @swarm.allow_filesystem_tools
140
- # Extract tool names from hashes and convert to symbols for comparison
141
- forbidden = tool_configs.map { |tc| tc[:name].to_sym }.select { |name| FILESYSTEM_TOOLS.include?(name) }
142
- unless forbidden.empty?
143
- raise ConfigurationError,
144
- "Filesystem tools are globally disabled (SwarmSDK.config.allow_filesystem_tools = false) " \
145
- "but agent '#{agent_name}' attempts to use: #{forbidden.join(", ")}.\n\n" \
146
- "This is a system-wide security setting that cannot be overridden by swarm configuration.\n" \
147
- "To use filesystem tools, set SwarmSDK.config.allow_filesystem_tools = true before loading the swarm."
148
- end
149
- end
150
-
151
- tool_configs.each do |tool_config|
152
- tool_name = tool_config[:name]
153
- permissions_config = tool_config[:permissions]
154
-
155
- # Create tool instance
156
- tool_instance = create_tool_instance(tool_name, agent_name, agent_definition.directory)
157
-
158
- # Wrap with permissions and add to chat
159
- wrap_and_add_tool(chat, tool_instance, permissions_config, agent_definition)
160
- end
161
- end
162
-
163
- # Register default tools for agents (unless disabled)
164
- #
165
- # Note: Memory tools are registered separately and are NOT affected by
166
- # disable_default_tools, since they're configured via memory {} block.
167
- #
168
- # @param chat [AgentChat] The chat instance
169
- # @param agent_name [Symbol] Agent name
170
- # @param agent_definition [AgentDefinition] Agent definition
171
- def register_default_tools(chat, agent_name:, agent_definition:)
172
- # Get explicit tool names to avoid duplicates
173
- explicit_tool_names = agent_definition.tools.map { |t| t[:name] }.to_set
174
-
175
- # Register core default tools (unless disabled)
176
- if agent_definition.disable_default_tools != true
177
- DEFAULT_TOOLS.each do |tool_name|
178
- # Skip filesystem tools if globally disabled
179
- next if !@swarm.allow_filesystem_tools && FILESYSTEM_TOOLS.include?(tool_name)
180
-
181
- register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
182
- end
183
-
184
- # Register scratchpad tools if enabled
185
- if @swarm.scratchpad_enabled?
186
- SCRATCHPAD_TOOLS.each do |tool_name|
187
- register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
188
- end
189
- end
190
- end
191
-
192
- # Register plugin tools if plugin storage is enabled for this agent
193
- # Plugin tools ARE affected by disable_default_tools (allows fine-grained control)
194
- register_plugin_tools(chat, agent_name, agent_definition, explicit_tool_names)
195
- end
196
-
197
- # Register a tool if not already explicit or disabled
198
- def register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
199
- # Skip if already registered explicitly
200
- return if explicit_tool_names.include?(tool_name)
201
-
202
- # Skip if tool is in the disable list
203
- return if tool_disabled?(tool_name, agent_definition.disable_default_tools)
204
-
205
- tool_instance = create_tool_instance(tool_name, agent_name, agent_definition.directory)
206
- permissions_config = resolve_default_permissions(tool_name, agent_definition)
207
-
208
- wrap_and_add_tool(chat, tool_instance, permissions_config, agent_definition)
209
- end
210
-
211
- # Wrap tool with permissions and add to chat
212
- #
213
- # This is the common pattern for registering tools:
214
- # 1. Wrap with permissions validator (if configured)
215
- # 2. Add to chat
216
- #
217
- # @param chat [Agent::Chat] The chat instance
218
- # @param tool_instance [RubyLLM::Tool] Tool instance
219
- # @param permissions_config [Hash, nil] Permissions configuration
220
- # @param agent_definition [Agent::Definition] Agent definition
221
- # @return [void]
222
- def wrap_and_add_tool(chat, tool_instance, permissions_config, agent_definition, source: :builtin, metadata: {})
223
- base_tool = tool_instance # Keep reference to unwrapped tool
224
- wrapped_tool = wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
225
-
226
- # Register in tool registry (Plan 025)
227
- chat.tool_registry.register(
228
- wrapped_tool,
229
- base_tool: base_tool,
230
- source: source,
231
- metadata: metadata.merge(permissions: permissions_config),
232
- )
233
- end
234
-
235
- # Resolve permissions for a default/plugin tool
236
- #
237
- # Looks up permissions in agent-specific config first, falls back to global defaults.
238
- #
239
- # @param tool_name [Symbol] Tool name
240
- # @param agent_definition [Agent::Definition] Agent definition
241
- # @return [Hash, nil] Permissions configuration or nil
242
- def resolve_default_permissions(tool_name, agent_definition)
243
- agent_definition.agent_permissions[tool_name] || agent_definition.default_permissions[tool_name]
244
- end
245
-
246
- # Create a tool instance via plugin
247
- #
248
- # @param tool_name [Symbol] Tool name
249
- # @param agent_name [Symbol] Agent name
250
- # @param directory [String] Working directory
251
- # @param chat [Agent::Chat, nil] Chat instance
252
- # @param agent_definition [Agent::Definition, nil] Agent definition
253
- # @return [RubyLLM::Tool] Tool instance
254
- def create_plugin_tool(tool_name, agent_name, directory, chat, agent_definition)
255
- plugin = PluginRegistry.plugin_for_tool(tool_name)
256
- raise ConfigurationError, "Tool #{tool_name} is not provided by any plugin" unless plugin
257
-
258
- # V7.0: Extract base name for storage lookup (handles delegation instances)
259
- # For primary agents: :tester → :tester (no change)
260
- # For delegation instances: "tester@frontend" → :tester (extracts base)
261
- base_name = agent_name.to_s.split("@").first.to_sym
262
-
263
- # Get plugin storage using BASE NAME (shared across instances)
264
- plugin_storages = @plugin_storages[plugin.name] || {}
265
- storage = plugin_storages[base_name] # ← Changed from agent_name to base_name
266
-
267
- # Build context for tool creation
268
- # Pass full agent_name for tool state tracking (TodoWrite, ReadTracker, etc.)
269
- context = {
270
- agent_name: agent_name, # Full instance name for tool's use
271
- directory: directory,
272
- storage: storage, # Shared storage by base name
273
- agent_definition: agent_definition,
274
- chat: chat,
275
- tool_configurator: self,
276
- }
277
-
278
- plugin.create_tool(tool_name, context)
279
- end
280
-
281
- # Register plugin-provided tools for an agent
282
- #
283
- # Asks all plugins if they have tools to register for this agent.
284
- #
285
- # @param chat [Agent::Chat] Chat instance
286
- # @param agent_name [Symbol] Agent name
287
- # @param agent_definition [Agent::Definition] Agent definition
288
- # @param explicit_tool_names [Set<Symbol>] Already-registered tool names
289
- def register_plugin_tools(chat, agent_name, agent_definition, explicit_tool_names)
290
- PluginRegistry.all.each do |plugin|
291
- # Check if plugin has storage enabled for this agent
292
- next unless plugin.memory_configured?(agent_definition)
293
-
294
- # Register each tool provided by the plugin
295
- plugin.tools.each do |tool_name|
296
- # Skip if already registered explicitly
297
- next if explicit_tool_names.include?(tool_name)
298
-
299
- # Skip if tool is disabled via disable_default_tools
300
- next if tool_disabled?(tool_name, agent_definition.disable_default_tools)
301
-
302
- tool_instance = create_tool_instance(
303
- tool_name,
304
- agent_name,
305
- agent_definition.directory,
306
- chat: chat,
307
- agent_definition: agent_definition,
308
- )
309
-
310
- permissions_config = resolve_default_permissions(tool_name, agent_definition)
311
-
312
- wrap_and_add_tool(
313
- chat,
314
- tool_instance,
315
- permissions_config,
316
- agent_definition,
317
- source: :plugin,
318
- metadata: { plugin_name: plugin.class.name },
319
- )
320
- end
321
- end
322
- end
323
-
324
- # Check if a tool should be disabled based on disable_default_tools config
325
- #
326
- # @param tool_name [Symbol] Tool name to check
327
- # @param disable_config [nil, Boolean, Symbol, Array<Symbol>] Disable configuration
328
- # @return [Boolean] True if tool should be disabled
329
- def tool_disabled?(tool_name, disable_config)
330
- return false if disable_config.nil?
331
-
332
- # Normalize tool_name to symbol for comparison
333
- tool_name_sym = tool_name.to_sym
334
-
335
- if disable_config == true
336
- # Disable all default tools
337
- true
338
- elsif disable_config.is_a?(Symbol)
339
- # Single tool name
340
- disable_config == tool_name_sym
341
- elsif disable_config.is_a?(String)
342
- # Single tool name as string (from YAML)
343
- disable_config.to_sym == tool_name_sym
344
- elsif disable_config.is_a?(Array)
345
- # Disable only tools in the array - normalize to symbols for comparison
346
- disable_config.map(&:to_sym).include?(tool_name_sym)
347
- else
348
- false
349
- end
350
- end
351
-
352
- # Register agent delegation tools
353
- #
354
- # Creates delegation tools that allow one agent to call another.
355
- #
356
- # @param chat [AgentChat] The chat instance
357
- # @param delegate_names [Array<Symbol>] Names of agents to delegate to
358
- # @param agent_name [Symbol] Name of the agent doing the delegating
359
- def register_delegation_tools(chat, delegate_names, agent_name:)
360
- return if delegate_names.empty?
361
-
362
- delegate_names.each do |delegate_name|
363
- delegate_name = delegate_name.to_sym
364
-
365
- unless @agents.key?(delegate_name)
366
- raise ConfigurationError, "Agent delegates to unknown agent '#{delegate_name}'"
367
- end
368
-
369
- # Create a tool that delegates to the specified agent
370
- delegate_agent = @agents[delegate_name]
371
- delegate_definition = @agent_definitions[delegate_name]
372
-
373
- tool = Tools::Delegate.new(
374
- delegate_name: delegate_name.to_s,
375
- delegate_description: delegate_definition.description,
376
- delegate_chat: delegate_agent,
377
- agent_name: agent_name,
378
- swarm: @swarm,
379
- delegating_chat: chat,
380
- )
381
-
382
- # Register in tool registry (Plan 025)
383
- chat.tool_registry.register(
384
- tool,
385
- source: :delegation,
386
- metadata: { delegate_name: delegate_name },
387
- )
388
- end
389
- end
390
- end
391
- end
392
- end