swarm_sdk 2.1.3 → 2.2.0
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/builder.rb +33 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +33 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +41 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +11 -27
- data/lib/swarm_sdk/agent/chat.rb +198 -51
- data/lib/swarm_sdk/agent/context.rb +6 -2
- data/lib/swarm_sdk/agent/context_manager.rb +6 -0
- data/lib/swarm_sdk/agent/definition.rb +14 -2
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +180 -0
- data/lib/swarm_sdk/configuration.rb +387 -94
- data/lib/swarm_sdk/events_to_messages.rb +181 -0
- data/lib/swarm_sdk/log_collector.rb +31 -5
- data/lib/swarm_sdk/log_stream.rb +37 -8
- data/lib/swarm_sdk/model_aliases.json +4 -1
- data/lib/swarm_sdk/node/agent_config.rb +33 -8
- data/lib/swarm_sdk/node/builder.rb +39 -18
- data/lib/swarm_sdk/node_orchestrator.rb +293 -26
- data/lib/swarm_sdk/proc_helpers.rb +53 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +22 -15
- data/lib/swarm_sdk/restore_result.rb +65 -0
- data/lib/swarm_sdk/snapshot.rb +156 -0
- data/lib/swarm_sdk/snapshot_from_events.rb +386 -0
- data/lib/swarm_sdk/state_restorer.rb +491 -0
- data/lib/swarm_sdk/state_snapshot.rb +369 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +360 -55
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
- data/lib/swarm_sdk/swarm/builder.rb +208 -12
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +46 -11
- data/lib/swarm_sdk/swarm.rb +337 -42
- data/lib/swarm_sdk/swarm_loader.rb +145 -0
- data/lib/swarm_sdk/swarm_registry.rb +136 -0
- data/lib/swarm_sdk/tools/delegate.rb +92 -7
- data/lib/swarm_sdk/tools/read.rb +17 -5
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +45 -0
- data/lib/swarm_sdk/utils.rb +18 -0
- data/lib/swarm_sdk/validation_result.rb +33 -0
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk.rb +40 -8
- metadata +17 -6
- data/lib/swarm_sdk/mcp.rb +0 -16
|
@@ -37,22 +37,32 @@ module SwarmSDK
|
|
|
37
37
|
# agent :backend { ... }
|
|
38
38
|
# end
|
|
39
39
|
class << self
|
|
40
|
-
def build(&block)
|
|
41
|
-
builder = new
|
|
40
|
+
def build(allow_filesystem_tools: nil, &block)
|
|
41
|
+
builder = new(allow_filesystem_tools: allow_filesystem_tools)
|
|
42
42
|
builder.instance_eval(&block)
|
|
43
43
|
builder.build_swarm
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
def initialize
|
|
47
|
+
def initialize(allow_filesystem_tools: nil)
|
|
48
|
+
@swarm_id = nil
|
|
48
49
|
@swarm_name = nil
|
|
49
50
|
@lead_agent = nil
|
|
50
51
|
@agents = {}
|
|
51
52
|
@all_agents_config = nil
|
|
52
53
|
@swarm_hooks = []
|
|
54
|
+
@swarm_registry_config = [] # NEW - stores register() calls for composable swarms
|
|
53
55
|
@nodes = {}
|
|
54
56
|
@start_node = nil
|
|
55
|
-
@
|
|
57
|
+
@scratchpad = :disabled # Default: disabled
|
|
58
|
+
@allow_filesystem_tools = allow_filesystem_tools
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Set swarm ID
|
|
62
|
+
#
|
|
63
|
+
# @param swarm_id [String] Unique identifier for this swarm
|
|
64
|
+
def id(swarm_id)
|
|
65
|
+
@swarm_id = swarm_id
|
|
56
66
|
end
|
|
57
67
|
|
|
58
68
|
# Set swarm name
|
|
@@ -65,11 +75,29 @@ module SwarmSDK
|
|
|
65
75
|
@lead_agent = agent_name
|
|
66
76
|
end
|
|
67
77
|
|
|
68
|
-
#
|
|
78
|
+
# Configure scratchpad mode
|
|
79
|
+
#
|
|
80
|
+
# For NodeOrchestrator: :enabled (shared across nodes), :per_node (isolated), or :disabled
|
|
81
|
+
# For regular Swarm: :enabled or :disabled
|
|
82
|
+
#
|
|
83
|
+
# @param mode [Symbol, Boolean] Scratchpad mode
|
|
84
|
+
def scratchpad(mode)
|
|
85
|
+
@scratchpad = mode
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Register external swarms for composable swarms
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# swarms do
|
|
92
|
+
# register "code_review", file: "./swarms/code_review.rb"
|
|
93
|
+
# register "testing", file: "./swarms/testing.yml", keep_context: false
|
|
94
|
+
# end
|
|
69
95
|
#
|
|
70
|
-
# @
|
|
71
|
-
def
|
|
72
|
-
|
|
96
|
+
# @yield Block containing register() calls
|
|
97
|
+
def swarms(&block)
|
|
98
|
+
builder = SwarmRegistryBuilder.new
|
|
99
|
+
builder.instance_eval(&block)
|
|
100
|
+
@swarm_registry_config = builder.registrations
|
|
73
101
|
end
|
|
74
102
|
|
|
75
103
|
# Define an agent with fluent API or load from markdown content
|
|
@@ -196,6 +224,12 @@ module SwarmSDK
|
|
|
196
224
|
def build_swarm
|
|
197
225
|
raise ConfigurationError, "Swarm name not set. Use: name 'My Swarm'" unless @swarm_name
|
|
198
226
|
|
|
227
|
+
# Validate all_agents filesystem tools BEFORE building
|
|
228
|
+
validate_all_agents_filesystem_tools if @all_agents_config
|
|
229
|
+
|
|
230
|
+
# Validate individual agent filesystem tools BEFORE building
|
|
231
|
+
validate_agent_filesystem_tools
|
|
232
|
+
|
|
199
233
|
# Check if nodes are defined
|
|
200
234
|
if @nodes.any?
|
|
201
235
|
# Node-based workflow (agents optional for agent-less workflows)
|
|
@@ -211,6 +245,26 @@ module SwarmSDK
|
|
|
211
245
|
|
|
212
246
|
private
|
|
213
247
|
|
|
248
|
+
# Normalize scratchpad mode parameter
|
|
249
|
+
#
|
|
250
|
+
# Accepts symbols: :enabled, :per_node, or :disabled
|
|
251
|
+
#
|
|
252
|
+
# @param value [Symbol, String] Scratchpad mode (strings from YAML converted to symbols)
|
|
253
|
+
# @return [Symbol] Normalized mode (:enabled, :per_node, or :disabled)
|
|
254
|
+
# @raise [ConfigurationError] If value is invalid
|
|
255
|
+
def normalize_scratchpad_mode(value)
|
|
256
|
+
# Convert strings from YAML to symbols
|
|
257
|
+
value = value.to_sym if value.is_a?(String)
|
|
258
|
+
|
|
259
|
+
case value
|
|
260
|
+
when :enabled, :per_node, :disabled
|
|
261
|
+
value
|
|
262
|
+
else
|
|
263
|
+
raise ConfigurationError,
|
|
264
|
+
"Invalid scratchpad mode: #{value.inspect}. Use :enabled, :per_node, or :disabled"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
214
268
|
# Check if a string is markdown content (has frontmatter)
|
|
215
269
|
#
|
|
216
270
|
# @param str [String] String to check
|
|
@@ -310,8 +364,27 @@ module SwarmSDK
|
|
|
310
364
|
#
|
|
311
365
|
# @return [Swarm] Configured swarm instance
|
|
312
366
|
def build_single_swarm
|
|
313
|
-
#
|
|
314
|
-
|
|
367
|
+
# Validate swarm_id is set if external swarms are registered (required for composable swarms)
|
|
368
|
+
if @swarm_registry_config.any? && @swarm_id.nil?
|
|
369
|
+
raise ConfigurationError, "Swarm id must be set using id(...) when using composable swarms"
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Create swarm using SDK (swarm_id auto-generates if nil)
|
|
373
|
+
swarm = Swarm.new(
|
|
374
|
+
name: @swarm_name,
|
|
375
|
+
swarm_id: @swarm_id,
|
|
376
|
+
scratchpad_mode: @scratchpad,
|
|
377
|
+
allow_filesystem_tools: @allow_filesystem_tools,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Setup swarm registry if external swarms are registered
|
|
381
|
+
if @swarm_registry_config.any?
|
|
382
|
+
registry = SwarmRegistry.new(parent_swarm_id: @swarm_id)
|
|
383
|
+
@swarm_registry_config.each do |reg|
|
|
384
|
+
registry.register(reg[:name], source: reg[:source], keep_context: reg[:keep_context])
|
|
385
|
+
end
|
|
386
|
+
swarm.swarm_registry = registry
|
|
387
|
+
end
|
|
315
388
|
|
|
316
389
|
# Merge all_agents config into each agent (including file-loaded ones)
|
|
317
390
|
merge_all_agents_config_into_agents if @all_agents_config
|
|
@@ -375,13 +448,20 @@ module SwarmSDK
|
|
|
375
448
|
end
|
|
376
449
|
|
|
377
450
|
# Create node orchestrator
|
|
378
|
-
NodeOrchestrator.new(
|
|
451
|
+
orchestrator = NodeOrchestrator.new(
|
|
379
452
|
swarm_name: @swarm_name,
|
|
453
|
+
swarm_id: @swarm_id,
|
|
380
454
|
agent_definitions: agent_definitions,
|
|
381
455
|
nodes: @nodes,
|
|
382
456
|
start_node: @start_node,
|
|
383
|
-
|
|
457
|
+
scratchpad: @scratchpad,
|
|
458
|
+
allow_filesystem_tools: @allow_filesystem_tools,
|
|
384
459
|
)
|
|
460
|
+
|
|
461
|
+
# Pass swarm registry config to orchestrator if external swarms registered
|
|
462
|
+
orchestrator.swarm_registry_config = @swarm_registry_config if @swarm_registry_config.any?
|
|
463
|
+
|
|
464
|
+
orchestrator
|
|
385
465
|
end
|
|
386
466
|
|
|
387
467
|
# Merge all_agents configuration into each agent
|
|
@@ -582,6 +662,122 @@ module SwarmSDK
|
|
|
582
662
|
base
|
|
583
663
|
end
|
|
584
664
|
end
|
|
665
|
+
|
|
666
|
+
# Validate all_agents filesystem tools
|
|
667
|
+
#
|
|
668
|
+
# Raises ConfigurationError if filesystem tools are globally disabled
|
|
669
|
+
# but all_agents configuration includes them.
|
|
670
|
+
#
|
|
671
|
+
# @raise [ConfigurationError] If filesystem tools are disabled and all_agents has them
|
|
672
|
+
# @return [void]
|
|
673
|
+
def validate_all_agents_filesystem_tools
|
|
674
|
+
# Resolve the effective setting
|
|
675
|
+
resolved_setting = if @allow_filesystem_tools.nil?
|
|
676
|
+
SwarmSDK.settings.allow_filesystem_tools
|
|
677
|
+
else
|
|
678
|
+
@allow_filesystem_tools
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
return if resolved_setting # If true, allow everything
|
|
682
|
+
return unless @all_agents_config&.tools_list&.any?
|
|
683
|
+
|
|
684
|
+
forbidden = @all_agents_config.tools_list.select do |tool|
|
|
685
|
+
SwarmSDK::Swarm::ToolConfigurator::FILESYSTEM_TOOLS.include?(tool.to_sym)
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
return if forbidden.empty?
|
|
689
|
+
|
|
690
|
+
raise ConfigurationError,
|
|
691
|
+
"Filesystem tools are globally disabled (SwarmSDK.settings.allow_filesystem_tools = false) " \
|
|
692
|
+
"but all_agents configuration includes: #{forbidden.join(", ")}.\n\n" \
|
|
693
|
+
"This is a system-wide security setting that cannot be overridden by swarm configuration.\n" \
|
|
694
|
+
"To use filesystem tools, set SwarmSDK.settings.allow_filesystem_tools = true before loading the swarm."
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# Validate individual agent filesystem tools
|
|
698
|
+
#
|
|
699
|
+
# Raises ConfigurationError if filesystem tools are globally disabled
|
|
700
|
+
# but any agent attempts to use them.
|
|
701
|
+
#
|
|
702
|
+
# @raise [ConfigurationError] If filesystem tools are disabled and any agent has them
|
|
703
|
+
# @return [void]
|
|
704
|
+
def validate_agent_filesystem_tools
|
|
705
|
+
# Resolve the effective setting
|
|
706
|
+
resolved_setting = if @allow_filesystem_tools.nil?
|
|
707
|
+
SwarmSDK.settings.allow_filesystem_tools
|
|
708
|
+
else
|
|
709
|
+
@allow_filesystem_tools
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
return if resolved_setting # If true, allow everything
|
|
713
|
+
|
|
714
|
+
# Check each agent for forbidden tools
|
|
715
|
+
@agents.each do |agent_name, agent_builder_or_config|
|
|
716
|
+
# Extract tool list from either Builder or file config
|
|
717
|
+
tools_list = if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
|
|
718
|
+
# File-loaded agent
|
|
719
|
+
agent_builder_or_config[:__file_config__][:tools] || []
|
|
720
|
+
elsif agent_builder_or_config.is_a?(Agent::Builder)
|
|
721
|
+
# Builder object - use tools_list method
|
|
722
|
+
agent_builder_or_config.tools_list
|
|
723
|
+
else
|
|
724
|
+
[]
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
# Extract tool names (they might be hashes with permissions) and convert to symbols
|
|
728
|
+
tool_names = tools_list.map do |tool|
|
|
729
|
+
name = tool.is_a?(Hash) ? tool[:name] : tool
|
|
730
|
+
name.to_sym
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
# Find forbidden tools
|
|
734
|
+
forbidden = tool_names.select do |tool|
|
|
735
|
+
SwarmSDK::Swarm::ToolConfigurator::FILESYSTEM_TOOLS.include?(tool)
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
next if forbidden.empty?
|
|
739
|
+
|
|
740
|
+
raise ConfigurationError,
|
|
741
|
+
"Filesystem tools are globally disabled (SwarmSDK.settings.allow_filesystem_tools = false) " \
|
|
742
|
+
"but agent '#{agent_name}' attempts to use: #{forbidden.join(", ")}.\n\n" \
|
|
743
|
+
"This is a system-wide security setting that cannot be overridden by swarm configuration.\n" \
|
|
744
|
+
"To use filesystem tools, set SwarmSDK.settings.allow_filesystem_tools = true before loading the swarm."
|
|
745
|
+
end
|
|
746
|
+
end
|
|
585
747
|
end
|
|
748
|
+
|
|
749
|
+
# Helper class for swarms block in DSL
|
|
750
|
+
#
|
|
751
|
+
# Provides a clean API for registering external swarms within the swarms { } block.
|
|
752
|
+
# Supports three registration methods:
|
|
753
|
+
# 1. File path: register "name", file: "./swarm.rb"
|
|
754
|
+
# 2. YAML string: register "name", yaml: "version: 2\n..."
|
|
755
|
+
# 3. Inline block: register "name" do ... end
|
|
756
|
+
#
|
|
757
|
+
# @example From file
|
|
758
|
+
# swarms do
|
|
759
|
+
# register "code_review", file: "./swarms/code_review.rb"
|
|
760
|
+
# end
|
|
761
|
+
#
|
|
762
|
+
# @example From YAML string
|
|
763
|
+
# swarms do
|
|
764
|
+
# yaml_content = File.read("testing.yml")
|
|
765
|
+
# register "testing", yaml: yaml_content, keep_context: false
|
|
766
|
+
# end
|
|
767
|
+
#
|
|
768
|
+
# @example Inline block
|
|
769
|
+
# swarms do
|
|
770
|
+
# register "testing", keep_context: false do
|
|
771
|
+
# id "testing_team"
|
|
772
|
+
# name "Testing Team"
|
|
773
|
+
# lead :tester
|
|
774
|
+
# agent :tester do
|
|
775
|
+
# model "gpt-4o-mini"
|
|
776
|
+
# system "You test code"
|
|
777
|
+
# end
|
|
778
|
+
# end
|
|
779
|
+
# end
|
|
780
|
+
#
|
|
781
|
+
# NOTE: SwarmRegistryBuilder is now in swarm_registry_builder.rb for Zeitwerk
|
|
586
782
|
end
|
|
587
783
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
|
@@ -17,10 +17,6 @@ module SwarmSDK
|
|
|
17
17
|
:Read,
|
|
18
18
|
:Grep,
|
|
19
19
|
:Glob,
|
|
20
|
-
:TodoWrite,
|
|
21
|
-
:Think,
|
|
22
|
-
:WebFetch,
|
|
23
|
-
:Clock,
|
|
24
20
|
].freeze
|
|
25
21
|
|
|
26
22
|
# Scratchpad tools (added if scratchpad is enabled)
|
|
@@ -30,6 +26,17 @@ module SwarmSDK
|
|
|
30
26
|
:ScratchpadList,
|
|
31
27
|
].freeze
|
|
32
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
|
+
|
|
33
40
|
def initialize(swarm, scratchpad_storage, plugin_storages = {})
|
|
34
41
|
@swarm = swarm
|
|
35
42
|
@scratchpad_storage = scratchpad_storage
|
|
@@ -144,6 +151,19 @@ module SwarmSDK
|
|
|
144
151
|
# @param agent_name [Symbol] Agent name
|
|
145
152
|
# @param agent_definition [AgentDefinition] Agent definition
|
|
146
153
|
def register_explicit_tools(chat, tool_configs, agent_name:, agent_definition:)
|
|
154
|
+
# Validate filesystem tools if globally disabled
|
|
155
|
+
unless @swarm.allow_filesystem_tools
|
|
156
|
+
# Extract tool names from hashes and convert to symbols for comparison
|
|
157
|
+
forbidden = tool_configs.map { |tc| tc[:name].to_sym }.select { |name| FILESYSTEM_TOOLS.include?(name) }
|
|
158
|
+
unless forbidden.empty?
|
|
159
|
+
raise ConfigurationError,
|
|
160
|
+
"Filesystem tools are globally disabled (SwarmSDK.settings.allow_filesystem_tools = false) " \
|
|
161
|
+
"but agent '#{agent_name}' attempts to use: #{forbidden.join(", ")}.\n\n" \
|
|
162
|
+
"This is a system-wide security setting that cannot be overridden by swarm configuration.\n" \
|
|
163
|
+
"To use filesystem tools, set SwarmSDK.settings.allow_filesystem_tools = true before loading the swarm."
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
147
167
|
tool_configs.each do |tool_config|
|
|
148
168
|
tool_name = tool_config[:name]
|
|
149
169
|
permissions_config = tool_config[:permissions]
|
|
@@ -177,6 +197,9 @@ module SwarmSDK
|
|
|
177
197
|
# Register core default tools (unless disabled)
|
|
178
198
|
if agent_definition.disable_default_tools != true
|
|
179
199
|
DEFAULT_TOOLS.each do |tool_name|
|
|
200
|
+
# Skip filesystem tools if globally disabled
|
|
201
|
+
next if !@swarm.allow_filesystem_tools && FILESYSTEM_TOOLS.include?(tool_name)
|
|
202
|
+
|
|
180
203
|
register_tool_if_not_disabled(chat, tool_name, explicit_tool_names, agent_name, agent_definition)
|
|
181
204
|
end
|
|
182
205
|
|
|
@@ -229,15 +252,21 @@ module SwarmSDK
|
|
|
229
252
|
plugin = PluginRegistry.plugin_for_tool(tool_name)
|
|
230
253
|
raise ConfigurationError, "Tool #{tool_name} is not provided by any plugin" unless plugin
|
|
231
254
|
|
|
232
|
-
#
|
|
255
|
+
# V7.0: Extract base name for storage lookup (handles delegation instances)
|
|
256
|
+
# For primary agents: :tester → :tester (no change)
|
|
257
|
+
# For delegation instances: "tester@frontend" → :tester (extracts base)
|
|
258
|
+
base_name = agent_name.to_s.split("@").first.to_sym
|
|
259
|
+
|
|
260
|
+
# Get plugin storage using BASE NAME (shared across instances)
|
|
233
261
|
plugin_storages = @plugin_storages[plugin.name] || {}
|
|
234
|
-
storage = plugin_storages[agent_name
|
|
262
|
+
storage = plugin_storages[base_name] # ← Changed from agent_name to base_name
|
|
235
263
|
|
|
236
264
|
# Build context for tool creation
|
|
265
|
+
# Pass full agent_name for tool state tracking (TodoWrite, ReadTracker, etc.)
|
|
237
266
|
context = {
|
|
238
|
-
agent_name: agent_name,
|
|
267
|
+
agent_name: agent_name, # Full instance name for tool's use
|
|
239
268
|
directory: directory,
|
|
240
|
-
storage: storage,
|
|
269
|
+
storage: storage, # Shared storage by base name
|
|
241
270
|
agent_definition: agent_definition,
|
|
242
271
|
chat: chat,
|
|
243
272
|
tool_configurator: self,
|
|
@@ -303,15 +332,21 @@ module SwarmSDK
|
|
|
303
332
|
def tool_disabled?(tool_name, disable_config)
|
|
304
333
|
return false if disable_config.nil?
|
|
305
334
|
|
|
335
|
+
# Normalize tool_name to symbol for comparison
|
|
336
|
+
tool_name_sym = tool_name.to_sym
|
|
337
|
+
|
|
306
338
|
if disable_config == true
|
|
307
339
|
# Disable all default tools
|
|
308
340
|
true
|
|
309
341
|
elsif disable_config.is_a?(Symbol)
|
|
310
342
|
# Single tool name
|
|
311
|
-
disable_config ==
|
|
343
|
+
disable_config == tool_name_sym
|
|
344
|
+
elsif disable_config.is_a?(String)
|
|
345
|
+
# Single tool name as string (from YAML)
|
|
346
|
+
disable_config.to_sym == tool_name_sym
|
|
312
347
|
elsif disable_config.is_a?(Array)
|
|
313
|
-
# Disable only tools in the array
|
|
314
|
-
disable_config.include?(
|
|
348
|
+
# Disable only tools in the array - normalize to symbols for comparison
|
|
349
|
+
disable_config.map(&:to_sym).include?(tool_name_sym)
|
|
315
350
|
else
|
|
316
351
|
false
|
|
317
352
|
end
|