swarm_sdk 2.0.6 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
- data/lib/swarm_sdk/agent/builder.rb +16 -42
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +43 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +41 -3
- data/lib/swarm_sdk/agent/chat.rb +426 -61
- data/lib/swarm_sdk/agent/context.rb +5 -1
- data/lib/swarm_sdk/agent/context_manager.rb +309 -0
- data/lib/swarm_sdk/agent/definition.rb +57 -24
- data/lib/swarm_sdk/plugin.rb +147 -0
- data/lib/swarm_sdk/plugin_registry.rb +101 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +7 -1
- data/lib/swarm_sdk/swarm/agent_initializer.rb +80 -12
- data/lib/swarm_sdk/swarm/tool_configurator.rb +116 -44
- data/lib/swarm_sdk/swarm.rb +44 -8
- data/lib/swarm_sdk/tools/clock.rb +44 -0
- data/lib/swarm_sdk/tools/grep.rb +16 -19
- data/lib/swarm_sdk/tools/registry.rb +23 -12
- data/lib/swarm_sdk/tools/todo_write.rb +1 -1
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk.rb +4 -0
- metadata +7 -12
- data/lib/swarm_sdk/prompts/memory.md.erb +0 -480
- data/lib/swarm_sdk/tools/memory/memory_delete.rb +0 -64
- data/lib/swarm_sdk/tools/memory/memory_edit.rb +0 -145
- data/lib/swarm_sdk/tools/memory/memory_glob.rb +0 -94
- data/lib/swarm_sdk/tools/memory/memory_grep.rb +0 -147
- data/lib/swarm_sdk/tools/memory/memory_multi_edit.rb +0 -228
- data/lib/swarm_sdk/tools/memory/memory_read.rb +0 -82
- data/lib/swarm_sdk/tools/memory/memory_write.rb +0 -90
- data/lib/swarm_sdk/tools/stores/memory_storage.rb +0 -300
- data/lib/swarm_sdk/tools/stores/storage_read_tracker.rb +0 -61
|
@@ -15,14 +15,14 @@ module SwarmSDK
|
|
|
15
15
|
# embedded in Swarm#initialize_agents.
|
|
16
16
|
class AgentInitializer
|
|
17
17
|
# rubocop:disable Metrics/ParameterLists
|
|
18
|
-
def initialize(swarm, agent_definitions, global_semaphore, hook_registry, scratchpad_storage,
|
|
18
|
+
def initialize(swarm, agent_definitions, global_semaphore, hook_registry, scratchpad_storage, plugin_storages, config_for_hooks: nil)
|
|
19
19
|
# rubocop:enable Metrics/ParameterLists
|
|
20
20
|
@swarm = swarm
|
|
21
21
|
@agent_definitions = agent_definitions
|
|
22
22
|
@global_semaphore = global_semaphore
|
|
23
23
|
@hook_registry = hook_registry
|
|
24
24
|
@scratchpad_storage = scratchpad_storage
|
|
25
|
-
@
|
|
25
|
+
@plugin_storages = plugin_storages
|
|
26
26
|
@config_for_hooks = config_for_hooks
|
|
27
27
|
@agents = {}
|
|
28
28
|
@agent_contexts = {}
|
|
@@ -80,21 +80,17 @@ module SwarmSDK
|
|
|
80
80
|
# This creates the Agent::Chat instances but doesn't wire them together yet.
|
|
81
81
|
# Each agent gets its own chat instance with configured tools.
|
|
82
82
|
def pass_1_create_agents
|
|
83
|
-
# Create
|
|
84
|
-
|
|
85
|
-
next unless agent_definition.memory_enabled?
|
|
86
|
-
|
|
87
|
-
# Use configured directory or default
|
|
88
|
-
memory_dir = agent_definition.memory.directory
|
|
89
|
-
memory_path = File.join(memory_dir, "memory.json")
|
|
90
|
-
@memory_storages[agent_name] = Tools::Stores::MemoryStorage.new(persist_to: memory_path)
|
|
91
|
-
end
|
|
83
|
+
# Create plugin storages for agents
|
|
84
|
+
create_plugin_storages
|
|
92
85
|
|
|
93
|
-
tool_configurator = ToolConfigurator.new(@swarm, @scratchpad_storage, @
|
|
86
|
+
tool_configurator = ToolConfigurator.new(@swarm, @scratchpad_storage, @plugin_storages)
|
|
94
87
|
|
|
95
88
|
@agent_definitions.each do |name, agent_definition|
|
|
96
89
|
chat = create_agent_chat(name, agent_definition, tool_configurator)
|
|
97
90
|
@agents[name] = chat
|
|
91
|
+
|
|
92
|
+
# Notify plugins that agent was initialized
|
|
93
|
+
notify_plugins_agent_initialized(name, chat, agent_definition, tool_configurator)
|
|
98
94
|
end
|
|
99
95
|
end
|
|
100
96
|
|
|
@@ -207,6 +203,7 @@ module SwarmSDK
|
|
|
207
203
|
def create_agent_chat(agent_name, agent_definition, tool_configurator)
|
|
208
204
|
chat = Agent::Chat.new(
|
|
209
205
|
definition: agent_definition.to_h,
|
|
206
|
+
agent_name: agent_name,
|
|
210
207
|
global_semaphore: @global_semaphore,
|
|
211
208
|
)
|
|
212
209
|
|
|
@@ -261,6 +258,77 @@ module SwarmSDK
|
|
|
261
258
|
chat.with_tool(tool)
|
|
262
259
|
end
|
|
263
260
|
end
|
|
261
|
+
|
|
262
|
+
# Create plugin storages for all agents
|
|
263
|
+
#
|
|
264
|
+
# Iterates through all registered plugins and asks each to create
|
|
265
|
+
# storage for agents that need it.
|
|
266
|
+
#
|
|
267
|
+
# @return [void]
|
|
268
|
+
def create_plugin_storages
|
|
269
|
+
PluginRegistry.all.each do |plugin|
|
|
270
|
+
@agent_definitions.each do |agent_name, agent_definition|
|
|
271
|
+
# Check if this plugin needs storage for this agent
|
|
272
|
+
next unless plugin.storage_enabled?(agent_definition)
|
|
273
|
+
|
|
274
|
+
# Get plugin config for this agent
|
|
275
|
+
config = get_plugin_config(agent_definition, plugin.name)
|
|
276
|
+
next unless config
|
|
277
|
+
|
|
278
|
+
# Parse config through plugin
|
|
279
|
+
parsed_config = plugin.parse_config(config)
|
|
280
|
+
|
|
281
|
+
# Create plugin storage
|
|
282
|
+
storage = plugin.create_storage(agent_name: agent_name, config: parsed_config)
|
|
283
|
+
|
|
284
|
+
# Store in plugin_storages: { plugin_name => { agent_name => storage } }
|
|
285
|
+
@plugin_storages[plugin.name] ||= {}
|
|
286
|
+
@plugin_storages[plugin.name][agent_name] = storage
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Get plugin-specific config from agent definition
|
|
292
|
+
#
|
|
293
|
+
# Plugins can store their config in agent definition under their plugin name.
|
|
294
|
+
# E.g., memory plugin looks for `agent_definition.memory`
|
|
295
|
+
#
|
|
296
|
+
# @param agent_definition [Agent::Definition] Agent definition
|
|
297
|
+
# @param plugin_name [Symbol] Plugin name
|
|
298
|
+
# @return [Object, nil] Plugin config or nil
|
|
299
|
+
def get_plugin_config(agent_definition, plugin_name)
|
|
300
|
+
# Try to call method named after plugin (e.g., .memory for :memory plugin)
|
|
301
|
+
if agent_definition.respond_to?(plugin_name)
|
|
302
|
+
agent_definition.public_send(plugin_name)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Notify all plugins that an agent was initialized
|
|
307
|
+
#
|
|
308
|
+
# Plugins can register additional tools, mark tools immutable, etc.
|
|
309
|
+
#
|
|
310
|
+
# @param agent_name [Symbol] Agent name
|
|
311
|
+
# @param chat [Agent::Chat] Chat instance
|
|
312
|
+
# @param agent_definition [Agent::Definition] Agent definition
|
|
313
|
+
# @param tool_configurator [ToolConfigurator] Tool configurator
|
|
314
|
+
# @return [void]
|
|
315
|
+
def notify_plugins_agent_initialized(agent_name, chat, agent_definition, tool_configurator)
|
|
316
|
+
PluginRegistry.all.each do |plugin|
|
|
317
|
+
# Get plugin storage for this agent (if any)
|
|
318
|
+
plugin_storages = @plugin_storages[plugin.name] || {}
|
|
319
|
+
storage = plugin_storages[agent_name]
|
|
320
|
+
|
|
321
|
+
# Build context for plugin
|
|
322
|
+
context = {
|
|
323
|
+
storage: storage,
|
|
324
|
+
agent_definition: agent_definition,
|
|
325
|
+
tool_configurator: tool_configurator,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# Notify plugin
|
|
329
|
+
plugin.on_agent_initialized(agent_name: agent_name, agent: chat, context: context)
|
|
330
|
+
end
|
|
331
|
+
end
|
|
264
332
|
end
|
|
265
333
|
end
|
|
266
334
|
end
|
|
@@ -20,6 +20,7 @@ module SwarmSDK
|
|
|
20
20
|
:TodoWrite,
|
|
21
21
|
:Think,
|
|
22
22
|
:WebFetch,
|
|
23
|
+
:Clock,
|
|
23
24
|
].freeze
|
|
24
25
|
|
|
25
26
|
# Scratchpad tools (added if scratchpad is enabled)
|
|
@@ -29,21 +30,12 @@ module SwarmSDK
|
|
|
29
30
|
:ScratchpadList,
|
|
30
31
|
].freeze
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
MEMORY_TOOLS = [
|
|
34
|
-
:MemoryWrite,
|
|
35
|
-
:MemoryRead,
|
|
36
|
-
:MemoryEdit,
|
|
37
|
-
:MemoryMultiEdit,
|
|
38
|
-
:MemoryGlob,
|
|
39
|
-
:MemoryGrep,
|
|
40
|
-
:MemoryDelete,
|
|
41
|
-
].freeze
|
|
42
|
-
|
|
43
|
-
def initialize(swarm, scratchpad_storage, memory_storages)
|
|
33
|
+
def initialize(swarm, scratchpad_storage, plugin_storages = {})
|
|
44
34
|
@swarm = swarm
|
|
45
35
|
@scratchpad_storage = scratchpad_storage
|
|
46
|
-
|
|
36
|
+
# Plugin storages: { plugin_name => { agent_name => storage } }
|
|
37
|
+
# e.g., { memory: { agent1: storage1, agent2: storage2 } }
|
|
38
|
+
@plugin_storages = plugin_storages
|
|
47
39
|
end
|
|
48
40
|
|
|
49
41
|
# Register all tools for an agent (both explicit and default)
|
|
@@ -60,16 +52,24 @@ module SwarmSDK
|
|
|
60
52
|
#
|
|
61
53
|
# File tools and TodoWrite require agent context for tracking state.
|
|
62
54
|
# Scratchpad tools require shared scratchpad instance.
|
|
55
|
+
# Plugin tools are delegated to their respective plugins.
|
|
63
56
|
#
|
|
64
57
|
# This method is public for testing delegation from Swarm.
|
|
65
58
|
#
|
|
66
59
|
# @param tool_name [Symbol, String] Tool name
|
|
67
60
|
# @param agent_name [Symbol] Agent name for context
|
|
68
61
|
# @param directory [String] Agent's working directory
|
|
62
|
+
# @param chat [Agent::Chat, nil] Optional chat instance for tools that need it
|
|
63
|
+
# @param agent_definition [Agent::Definition, nil] Optional agent definition
|
|
69
64
|
# @return [RubyLLM::Tool] Tool instance
|
|
70
|
-
def create_tool_instance(tool_name, agent_name, directory)
|
|
65
|
+
def create_tool_instance(tool_name, agent_name, directory, chat: nil, agent_definition: nil)
|
|
71
66
|
tool_name_sym = tool_name.to_sym
|
|
72
67
|
|
|
68
|
+
# Check if tool is provided by a plugin
|
|
69
|
+
if PluginRegistry.plugin_tool?(tool_name_sym)
|
|
70
|
+
return create_plugin_tool(tool_name_sym, agent_name, directory, chat, agent_definition)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
73
|
case tool_name_sym
|
|
74
74
|
when :Read
|
|
75
75
|
Tools::Read.new(agent_name: agent_name, directory: directory)
|
|
@@ -93,27 +93,22 @@ module SwarmSDK
|
|
|
93
93
|
Tools::Scratchpad::ScratchpadRead.create_for_scratchpad(@scratchpad_storage)
|
|
94
94
|
when :ScratchpadList
|
|
95
95
|
Tools::Scratchpad::ScratchpadList.create_for_scratchpad(@scratchpad_storage)
|
|
96
|
-
when :MemoryWrite
|
|
97
|
-
Tools::Memory::MemoryWrite.create_for_memory(@memory_storages[agent_name])
|
|
98
|
-
when :MemoryRead
|
|
99
|
-
Tools::Memory::MemoryRead.create_for_memory(@memory_storages[agent_name], agent_name)
|
|
100
|
-
when :MemoryEdit
|
|
101
|
-
Tools::Memory::MemoryEdit.create_for_memory(@memory_storages[agent_name], agent_name)
|
|
102
|
-
when :MemoryMultiEdit
|
|
103
|
-
Tools::Memory::MemoryMultiEdit.create_for_memory(@memory_storages[agent_name], agent_name)
|
|
104
|
-
when :MemoryDelete
|
|
105
|
-
Tools::Memory::MemoryDelete.create_for_memory(@memory_storages[agent_name])
|
|
106
|
-
when :MemoryGlob
|
|
107
|
-
Tools::Memory::MemoryGlob.create_for_memory(@memory_storages[agent_name])
|
|
108
|
-
when :MemoryGrep
|
|
109
|
-
Tools::Memory::MemoryGrep.create_for_memory(@memory_storages[agent_name])
|
|
110
96
|
when :Think
|
|
111
97
|
Tools::Think.new
|
|
98
|
+
when :Clock
|
|
99
|
+
Tools::Clock.new
|
|
112
100
|
else
|
|
113
101
|
# Regular tools - get class from registry and instantiate
|
|
114
102
|
tool_class = Tools::Registry.get(tool_name_sym)
|
|
115
103
|
raise ConfigurationError, "Unknown tool: #{tool_name}" unless tool_class
|
|
116
104
|
|
|
105
|
+
# Check if tool is marked as :special but not handled in case statement
|
|
106
|
+
if tool_class == :special
|
|
107
|
+
raise ConfigurationError,
|
|
108
|
+
"Tool '#{tool_name}' requires special initialization but is not handled in create_tool_instance. " \
|
|
109
|
+
"This is a bug - #{tool_name} should be added to the case statement above."
|
|
110
|
+
end
|
|
111
|
+
|
|
117
112
|
tool_class.new
|
|
118
113
|
end
|
|
119
114
|
end
|
|
@@ -169,34 +164,33 @@ module SwarmSDK
|
|
|
169
164
|
|
|
170
165
|
# Register default tools for agents (unless disabled)
|
|
171
166
|
#
|
|
167
|
+
# Note: Memory tools are registered separately and are NOT affected by
|
|
168
|
+
# disable_default_tools, since they're configured via memory {} block.
|
|
169
|
+
#
|
|
172
170
|
# @param chat [AgentChat] The chat instance
|
|
173
171
|
# @param agent_name [Symbol] Agent name
|
|
174
172
|
# @param agent_definition [AgentDefinition] Agent definition
|
|
175
173
|
def register_default_tools(chat, agent_name:, agent_definition:)
|
|
176
|
-
# If disable_default_tools is true, skip all default tools
|
|
177
|
-
return if agent_definition.disable_default_tools == true
|
|
178
|
-
|
|
179
174
|
# Get explicit tool names to avoid duplicates
|
|
180
175
|
explicit_tool_names = agent_definition.tools.map { |t| t[:name] }.to_set
|
|
181
176
|
|
|
182
|
-
# Register core default tools
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Register scratchpad tools if enabled
|
|
188
|
-
if @swarm.scratchpad_enabled?
|
|
189
|
-
SCRATCHPAD_TOOLS.each do |tool_name|
|
|
177
|
+
# Register core default tools (unless disabled)
|
|
178
|
+
if agent_definition.disable_default_tools != true
|
|
179
|
+
DEFAULT_TOOLS.each do |tool_name|
|
|
190
180
|
register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
|
|
191
181
|
end
|
|
192
|
-
end
|
|
193
182
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
183
|
+
# Register scratchpad tools if enabled
|
|
184
|
+
if @swarm.scratchpad_enabled?
|
|
185
|
+
SCRATCHPAD_TOOLS.each do |tool_name|
|
|
186
|
+
register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
|
|
187
|
+
end
|
|
198
188
|
end
|
|
199
189
|
end
|
|
190
|
+
|
|
191
|
+
# Register plugin tools if plugin storage is enabled for this agent
|
|
192
|
+
# Plugin tools ARE affected by disable_default_tools (allows fine-grained control)
|
|
193
|
+
register_plugin_tools(chat, agent_name, agent_definition, explicit_tool_names)
|
|
200
194
|
end
|
|
201
195
|
|
|
202
196
|
# Register a tool if not already explicit or disabled
|
|
@@ -223,6 +217,84 @@ module SwarmSDK
|
|
|
223
217
|
chat.with_tool(tool_instance)
|
|
224
218
|
end
|
|
225
219
|
|
|
220
|
+
# Create a tool instance via plugin
|
|
221
|
+
#
|
|
222
|
+
# @param tool_name [Symbol] Tool name
|
|
223
|
+
# @param agent_name [Symbol] Agent name
|
|
224
|
+
# @param directory [String] Working directory
|
|
225
|
+
# @param chat [Agent::Chat, nil] Chat instance
|
|
226
|
+
# @param agent_definition [Agent::Definition, nil] Agent definition
|
|
227
|
+
# @return [RubyLLM::Tool] Tool instance
|
|
228
|
+
def create_plugin_tool(tool_name, agent_name, directory, chat, agent_definition)
|
|
229
|
+
plugin = PluginRegistry.plugin_for_tool(tool_name)
|
|
230
|
+
raise ConfigurationError, "Tool #{tool_name} is not provided by any plugin" unless plugin
|
|
231
|
+
|
|
232
|
+
# Get plugin storage for this agent
|
|
233
|
+
plugin_storages = @plugin_storages[plugin.name] || {}
|
|
234
|
+
storage = plugin_storages[agent_name]
|
|
235
|
+
|
|
236
|
+
# Build context for tool creation
|
|
237
|
+
context = {
|
|
238
|
+
agent_name: agent_name,
|
|
239
|
+
directory: directory,
|
|
240
|
+
storage: storage,
|
|
241
|
+
agent_definition: agent_definition,
|
|
242
|
+
chat: chat,
|
|
243
|
+
tool_configurator: self,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
plugin.create_tool(tool_name, context)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Register plugin-provided tools for an agent
|
|
250
|
+
#
|
|
251
|
+
# Asks all plugins if they have tools to register for this agent.
|
|
252
|
+
#
|
|
253
|
+
# @param chat [Agent::Chat] Chat instance
|
|
254
|
+
# @param agent_name [Symbol] Agent name
|
|
255
|
+
# @param agent_definition [Agent::Definition] Agent definition
|
|
256
|
+
# @param explicit_tool_names [Set<Symbol>] Already-registered tool names
|
|
257
|
+
def register_plugin_tools(chat, agent_name, agent_definition, explicit_tool_names)
|
|
258
|
+
PluginRegistry.all.each do |plugin|
|
|
259
|
+
# Check if plugin has storage enabled for this agent
|
|
260
|
+
next unless plugin.storage_enabled?(agent_definition)
|
|
261
|
+
|
|
262
|
+
# Get plugin storage for this agent
|
|
263
|
+
plugin_storages = @plugin_storages[plugin.name] || {}
|
|
264
|
+
plugin_storages[agent_name]
|
|
265
|
+
|
|
266
|
+
# Register each tool provided by the plugin
|
|
267
|
+
plugin.tools.each do |tool_name|
|
|
268
|
+
# Skip if already registered explicitly
|
|
269
|
+
next if explicit_tool_names.include?(tool_name)
|
|
270
|
+
|
|
271
|
+
# Skip if tool is disabled via disable_default_tools
|
|
272
|
+
next if tool_disabled?(tool_name, agent_definition.disable_default_tools)
|
|
273
|
+
|
|
274
|
+
tool_instance = create_tool_instance(
|
|
275
|
+
tool_name,
|
|
276
|
+
agent_name,
|
|
277
|
+
agent_definition.directory,
|
|
278
|
+
chat: chat,
|
|
279
|
+
agent_definition: agent_definition,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Resolve permissions for plugin tool
|
|
283
|
+
permissions_config = agent_definition.agent_permissions[tool_name] ||
|
|
284
|
+
agent_definition.default_permissions[tool_name]
|
|
285
|
+
|
|
286
|
+
# Wrap with permissions validator if configured
|
|
287
|
+
tool_instance = wrap_tool_with_permissions(
|
|
288
|
+
tool_instance,
|
|
289
|
+
permissions_config,
|
|
290
|
+
agent_definition,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
chat.with_tool(tool_instance)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
226
298
|
# Check if a tool should be disabled based on disable_default_tools config
|
|
227
299
|
#
|
|
228
300
|
# @param tool_name [Symbol] Tool name to check
|
data/lib/swarm_sdk/swarm.rb
CHANGED
|
@@ -151,9 +151,10 @@ module SwarmSDK
|
|
|
151
151
|
# Use provided scratchpad storage (for testing) or create volatile one
|
|
152
152
|
@scratchpad_storage = scratchpad || Tools::Stores::ScratchpadStorage.new
|
|
153
153
|
|
|
154
|
-
# Per-agent
|
|
154
|
+
# Per-agent plugin storages (persistent)
|
|
155
|
+
# Format: { plugin_name => { agent_name => storage } }
|
|
155
156
|
# Will be populated when agents are initialized
|
|
156
|
-
@
|
|
157
|
+
@plugin_storages = {}
|
|
157
158
|
|
|
158
159
|
# Hook registry for named hooks and swarm defaults
|
|
159
160
|
@hook_registry = Hooks::Registry.new
|
|
@@ -551,7 +552,7 @@ module SwarmSDK
|
|
|
551
552
|
@global_semaphore,
|
|
552
553
|
@hook_registry,
|
|
553
554
|
@scratchpad_storage,
|
|
554
|
-
@
|
|
555
|
+
@plugin_storages,
|
|
555
556
|
config_for_hooks: @config_for_hooks,
|
|
556
557
|
)
|
|
557
558
|
|
|
@@ -571,6 +572,18 @@ module SwarmSDK
|
|
|
571
572
|
@agents.each do |agent_name, chat|
|
|
572
573
|
agent_def = @agent_definitions[agent_name]
|
|
573
574
|
|
|
575
|
+
# Build plugin storage info for logging
|
|
576
|
+
plugin_storage_info = {}
|
|
577
|
+
@plugin_storages.each do |plugin_name, agent_storages|
|
|
578
|
+
next unless agent_storages.key?(agent_name)
|
|
579
|
+
|
|
580
|
+
plugin_storage_info[plugin_name] = {
|
|
581
|
+
enabled: true,
|
|
582
|
+
# Get additional info from agent definition if available
|
|
583
|
+
config: agent_def.respond_to?(plugin_name) ? extract_plugin_config_info(agent_def.public_send(plugin_name)) : nil,
|
|
584
|
+
}
|
|
585
|
+
end
|
|
586
|
+
|
|
574
587
|
LogStream.emit(
|
|
575
588
|
type: "agent_start",
|
|
576
589
|
agent: agent_name,
|
|
@@ -581,8 +594,7 @@ module SwarmSDK
|
|
|
581
594
|
system_prompt: agent_def.system_prompt,
|
|
582
595
|
tools: chat.tools.keys,
|
|
583
596
|
delegates_to: agent_def.delegates_to,
|
|
584
|
-
|
|
585
|
-
memory_directory: agent_def.memory_enabled? ? agent_def.memory.directory : nil,
|
|
597
|
+
plugin_storages: plugin_storage_info,
|
|
586
598
|
timestamp: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
587
599
|
)
|
|
588
600
|
end
|
|
@@ -620,12 +632,12 @@ module SwarmSDK
|
|
|
620
632
|
|
|
621
633
|
# Create a tool instance (delegates to ToolConfigurator)
|
|
622
634
|
def create_tool_instance(tool_name, agent_name, directory)
|
|
623
|
-
ToolConfigurator.new(self, @scratchpad_storage, @
|
|
635
|
+
ToolConfigurator.new(self, @scratchpad_storage, @plugin_storages).create_tool_instance(tool_name, agent_name, directory)
|
|
624
636
|
end
|
|
625
637
|
|
|
626
638
|
# Wrap tool with permissions (delegates to ToolConfigurator)
|
|
627
639
|
def wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
|
|
628
|
-
ToolConfigurator.new(self, @scratchpad_storage, @
|
|
640
|
+
ToolConfigurator.new(self, @scratchpad_storage, @plugin_storages).wrap_tool_with_permissions(tool_instance, permissions_config, agent_definition)
|
|
629
641
|
end
|
|
630
642
|
|
|
631
643
|
# Build MCP transport config (delegates to McpConfigurator)
|
|
@@ -635,10 +647,34 @@ module SwarmSDK
|
|
|
635
647
|
|
|
636
648
|
# Create delegation tool (delegates to AgentInitializer)
|
|
637
649
|
def create_delegation_tool(name:, description:, delegate_chat:, agent_name:)
|
|
638
|
-
AgentInitializer.new(self, @agent_definitions, @global_semaphore, @hook_registry, @scratchpad_storage, @
|
|
650
|
+
AgentInitializer.new(self, @agent_definitions, @global_semaphore, @hook_registry, @scratchpad_storage, @plugin_storages)
|
|
639
651
|
.create_delegation_tool(name: name, description: description, delegate_chat: delegate_chat, agent_name: agent_name)
|
|
640
652
|
end
|
|
641
653
|
|
|
654
|
+
# Extract loggable info from plugin config
|
|
655
|
+
#
|
|
656
|
+
# Attempts to extract useful information from plugin configuration
|
|
657
|
+
# for logging purposes. Handles MemoryConfig, Hashes, and other objects.
|
|
658
|
+
#
|
|
659
|
+
# @param config [Object] Plugin configuration object
|
|
660
|
+
# @return [Hash, nil] Extracted config info or nil
|
|
661
|
+
def extract_plugin_config_info(config)
|
|
662
|
+
return if config.nil?
|
|
663
|
+
|
|
664
|
+
# Handle MemoryConfig object (has directory method)
|
|
665
|
+
if config.respond_to?(:directory)
|
|
666
|
+
return { directory: config.directory }
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
# Handle Hash
|
|
670
|
+
if config.is_a?(Hash)
|
|
671
|
+
return config.slice(:directory, "directory", :adapter, "adapter")
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
# Unknown config type
|
|
675
|
+
nil
|
|
676
|
+
end
|
|
677
|
+
|
|
642
678
|
# Register default logging hooks that emit LogStream events
|
|
643
679
|
#
|
|
644
680
|
# These hooks implement the standard SwarmSDK logging behavior.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
module Tools
|
|
5
|
+
# Clock tool provides current date and time information
|
|
6
|
+
#
|
|
7
|
+
# Returns current temporal information in a consistent format.
|
|
8
|
+
# Agents use this when they need to know what day/time it is.
|
|
9
|
+
class Clock < RubyLLM::Tool
|
|
10
|
+
description <<~DESC
|
|
11
|
+
Get current date and time.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
- Current date (YYYY-MM-DD format)
|
|
15
|
+
- Current time (HH:MM:SS format)
|
|
16
|
+
- Day of week (Monday, Tuesday, etc.)
|
|
17
|
+
- ISO 8601 timestamp (full datetime)
|
|
18
|
+
|
|
19
|
+
Use this when you need to know what day it is, what time it is,
|
|
20
|
+
or to store temporal information (e.g., "As of 2025-10-20...").
|
|
21
|
+
|
|
22
|
+
No parameters needed - just call Clock() to get complete temporal information.
|
|
23
|
+
DESC
|
|
24
|
+
|
|
25
|
+
# No parameters needed
|
|
26
|
+
|
|
27
|
+
# Override name to return simple "Clock"
|
|
28
|
+
def name
|
|
29
|
+
"Clock"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def execute
|
|
33
|
+
now = Time.now
|
|
34
|
+
|
|
35
|
+
<<~RESULT.chomp
|
|
36
|
+
Current date: #{now.strftime("%Y-%m-%d")}
|
|
37
|
+
Current time: #{now.strftime("%H:%M:%S")}
|
|
38
|
+
Day of week: #{now.strftime("%A")}
|
|
39
|
+
ISO 8601: #{now.iso8601}
|
|
40
|
+
RESULT
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/swarm_sdk/tools/grep.rb
CHANGED
|
@@ -46,15 +46,15 @@ module SwarmSDK
|
|
|
46
46
|
|
|
47
47
|
param :type,
|
|
48
48
|
type: "string",
|
|
49
|
-
desc: "File type to search (rg --type). Common types: js, py, rust,
|
|
49
|
+
desc: "File type to search (rg --type). Common types: c, cpp, cs, csharp, css, dart, docker, dockercompose, elixir, erlang, go, graphql, haskell, html, java, js, json, kotlin, lua, make, markdown, md, php, py, python, ruby, rust, sass, scala, sh, sql, svelte, swift, tf, toml, ts, typescript, vim, vue, xml, yaml, zig",
|
|
50
50
|
required: false
|
|
51
51
|
|
|
52
52
|
param :output_mode,
|
|
53
53
|
type: "string",
|
|
54
|
-
desc: "Output mode: \"content\" shows matching lines (supports
|
|
54
|
+
desc: "Output mode: \"content\" shows matching lines (supports context/line number options), \"files_with_matches\" shows file paths (default), \"count\" shows match counts. All modes support head_limit.",
|
|
55
55
|
required: false
|
|
56
56
|
|
|
57
|
-
param :
|
|
57
|
+
param :case_insensitive,
|
|
58
58
|
type: "boolean",
|
|
59
59
|
desc: "Case insensitive search (rg -i)",
|
|
60
60
|
required: false
|
|
@@ -64,22 +64,22 @@ module SwarmSDK
|
|
|
64
64
|
desc: "Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall)",
|
|
65
65
|
required: false
|
|
66
66
|
|
|
67
|
-
param :
|
|
67
|
+
param :context_before,
|
|
68
68
|
type: "integer",
|
|
69
69
|
desc: "Number of lines to show before each match (rg -B). Requires output_mode: \"content\", ignored otherwise.",
|
|
70
70
|
required: false
|
|
71
71
|
|
|
72
|
-
param :
|
|
72
|
+
param :context_after,
|
|
73
73
|
type: "integer",
|
|
74
74
|
desc: "Number of lines to show after each match (rg -A). Requires output_mode: \"content\", ignored otherwise.",
|
|
75
75
|
required: false
|
|
76
76
|
|
|
77
|
-
param :
|
|
77
|
+
param :context,
|
|
78
78
|
type: "integer",
|
|
79
79
|
desc: "Number of lines to show before and after each match (rg -C). Requires output_mode: \"content\", ignored otherwise.",
|
|
80
80
|
required: false
|
|
81
81
|
|
|
82
|
-
param :
|
|
82
|
+
param :show_line_numbers,
|
|
83
83
|
type: "boolean",
|
|
84
84
|
desc: "Show line numbers in output (rg -n). Requires output_mode: \"content\", ignored otherwise.",
|
|
85
85
|
required: false
|
|
@@ -95,7 +95,13 @@ module SwarmSDK
|
|
|
95
95
|
glob: nil,
|
|
96
96
|
type: nil,
|
|
97
97
|
output_mode: "files_with_matches",
|
|
98
|
-
|
|
98
|
+
case_insensitive: false,
|
|
99
|
+
multiline: false,
|
|
100
|
+
context_before: nil,
|
|
101
|
+
context_after: nil,
|
|
102
|
+
context: nil,
|
|
103
|
+
show_line_numbers: false,
|
|
104
|
+
head_limit: nil
|
|
99
105
|
)
|
|
100
106
|
# Validate inputs
|
|
101
107
|
return validation_error("pattern is required") if pattern.nil? || pattern.empty?
|
|
@@ -108,15 +114,6 @@ module SwarmSDK
|
|
|
108
114
|
resolve_path(path)
|
|
109
115
|
end
|
|
110
116
|
|
|
111
|
-
# Extract options with their flag names
|
|
112
|
-
case_insensitive = options["-i"] || false
|
|
113
|
-
multiline = options[:multiline] || false
|
|
114
|
-
context_before = options["-B"]
|
|
115
|
-
context_after = options["-A"]
|
|
116
|
-
context = options["-C"]
|
|
117
|
-
line_numbers = options["-n"] || false
|
|
118
|
-
head_limit = options[:head_limit]
|
|
119
|
-
|
|
120
117
|
# Validate output_mode
|
|
121
118
|
valid_modes = ["content", "files_with_matches", "count"]
|
|
122
119
|
unless valid_modes.include?(output_mode)
|
|
@@ -135,7 +132,7 @@ module SwarmSDK
|
|
|
135
132
|
when "content"
|
|
136
133
|
# Default mode, no special flag needed
|
|
137
134
|
# Add line numbers if requested
|
|
138
|
-
cmd << "-n" if
|
|
135
|
+
cmd << "-n" if show_line_numbers
|
|
139
136
|
|
|
140
137
|
# Add context flags
|
|
141
138
|
cmd << "-B" << context_before.to_s if context_before
|
|
@@ -222,7 +219,7 @@ module SwarmSDK
|
|
|
222
219
|
<system-reminder>
|
|
223
220
|
You used output_mode: '#{output_mode}' which only shows #{output_mode == "files_with_matches" ? "file paths" : "match counts"}.
|
|
224
221
|
To see the actual matching lines and their content, use output_mode: 'content'.
|
|
225
|
-
You can also add
|
|
222
|
+
You can also add show_line_numbers: true and context lines (context_before, context_after, or context) for better context.
|
|
226
223
|
</system-reminder>
|
|
227
224
|
REMINDER
|
|
228
225
|
end
|
|
@@ -6,6 +6,9 @@ module SwarmSDK
|
|
|
6
6
|
#
|
|
7
7
|
# Maps tool names (symbols) to their RubyLLM::Tool classes.
|
|
8
8
|
# Provides validation and lookup functionality for tool registration.
|
|
9
|
+
#
|
|
10
|
+
# Note: Plugin-provided tools (e.g., memory tools) are NOT in this registry.
|
|
11
|
+
# They are registered via SwarmSDK::PluginRegistry instead.
|
|
9
12
|
class Registry
|
|
10
13
|
# All available built-in tools
|
|
11
14
|
BUILTIN_TOOLS = {
|
|
@@ -20,24 +23,22 @@ module SwarmSDK
|
|
|
20
23
|
ScratchpadWrite: :special, # Requires scratchpad storage instance
|
|
21
24
|
ScratchpadRead: :special, # Requires scratchpad storage instance
|
|
22
25
|
ScratchpadList: :special, # Requires scratchpad storage instance
|
|
23
|
-
MemoryWrite: :special, # Requires memory storage instance
|
|
24
|
-
MemoryRead: :special, # Requires memory storage instance
|
|
25
|
-
MemoryEdit: :special, # Requires memory storage instance
|
|
26
|
-
MemoryMultiEdit: :special, # Requires memory storage instance
|
|
27
|
-
MemoryDelete: :special, # Requires memory storage instance
|
|
28
|
-
MemoryGlob: :special, # Requires memory storage instance
|
|
29
|
-
MemoryGrep: :special, # Requires memory storage instance
|
|
30
26
|
Think: SwarmSDK::Tools::Think,
|
|
31
27
|
WebFetch: SwarmSDK::Tools::WebFetch,
|
|
28
|
+
Clock: SwarmSDK::Tools::Clock,
|
|
32
29
|
}.freeze
|
|
33
30
|
|
|
34
31
|
class << self
|
|
35
32
|
# Get tool class by name
|
|
36
33
|
#
|
|
34
|
+
# Note: Plugin-provided tools are NOT returned by this method.
|
|
35
|
+
# They are managed by SwarmSDK::PluginRegistry instead.
|
|
36
|
+
#
|
|
37
37
|
# @param name [Symbol, String] Tool name
|
|
38
|
-
# @return [Class, nil] Tool class or nil if not found
|
|
38
|
+
# @return [Class, Symbol, nil] Tool class, :special, or nil if not found
|
|
39
39
|
def get(name)
|
|
40
|
-
|
|
40
|
+
name_sym = name.to_sym
|
|
41
|
+
BUILTIN_TOOLS[name_sym]
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
# Get multiple tool classes by names
|
|
@@ -48,7 +49,10 @@ module SwarmSDK
|
|
|
48
49
|
def get_many(names)
|
|
49
50
|
names.map do |name|
|
|
50
51
|
tool_class = get(name)
|
|
51
|
-
|
|
52
|
+
unless tool_class
|
|
53
|
+
raise ConfigurationError,
|
|
54
|
+
"Unknown tool: #{name}. Available tools: #{available_names.join(", ")}"
|
|
55
|
+
end
|
|
52
56
|
|
|
53
57
|
tool_class
|
|
54
58
|
end
|
|
@@ -56,13 +60,20 @@ module SwarmSDK
|
|
|
56
60
|
|
|
57
61
|
# Check if a tool exists
|
|
58
62
|
#
|
|
63
|
+
# Note: Only checks built-in tools. Plugin-provided tools are checked
|
|
64
|
+
# via SwarmSDK::PluginRegistry.plugin_tool?() instead.
|
|
65
|
+
#
|
|
59
66
|
# @param name [Symbol, String] Tool name
|
|
60
67
|
# @return [Boolean]
|
|
61
68
|
def exists?(name)
|
|
62
|
-
|
|
69
|
+
name_sym = name.to_sym
|
|
70
|
+
BUILTIN_TOOLS.key?(name_sym)
|
|
63
71
|
end
|
|
64
72
|
|
|
65
|
-
# Get all available tool names
|
|
73
|
+
# Get all available built-in tool names
|
|
74
|
+
#
|
|
75
|
+
# Note: Does NOT include plugin-provided tools. To get all available tools
|
|
76
|
+
# including plugins, combine with SwarmSDK::PluginRegistry.tools.
|
|
66
77
|
#
|
|
67
78
|
# @return [Array<Symbol>]
|
|
68
79
|
def available_names
|