claude_swarm 0.3.0 → 0.3.2
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/CHANGELOG.md +26 -0
- data/CLAUDE.md +2 -7
- data/README.md +5 -1
- data/lib/claude_swarm/claude_code_executor.rb +35 -30
- data/lib/claude_swarm/claude_mcp_server.rb +3 -2
- data/lib/claude_swarm/cli.rb +11 -0
- data/lib/claude_swarm/mcp_generator.rb +46 -1
- data/lib/claude_swarm/openai/executor.rb +17 -12
- data/lib/claude_swarm/orchestrator.rb +50 -37
- data/lib/claude_swarm/tools/task_tool.rb +10 -2
- data/lib/claude_swarm/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b271585c56ae3f85e921a7558bd46d82430ee60383fc999b6e89f27ffcdd0b0
|
4
|
+
data.tar.gz: 6b84e34b59412155d1a43cc00e8c41fe989287da7b98e2b12f8e1576f2002f87
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98c7d8d77ea9fb5256556fe7e73f83194cca4893084cab5b9b1826c6426052afd12b86f4fabad28e8e35d36a0114f6f7d6e1cd25fd4f02fb40f4450a5f048940
|
7
|
+
data.tar.gz: 33c134e8f553510dfee481b935c67eae21e196c4bc8bc494657042e69e32b91238169feaf0b138a632abda219fef791ad224294da958732c135b04a9fc7a1cf0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
## [0.3.2]
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- **Thinking budget support**: When delegating tasks between instances, orchestrators can now leverage Claude's extended thinking feature
|
5
|
+
- Connected instances automatically support thinking budgets: "think", "think hard", "think harder", "ultrathink"
|
6
|
+
- Orchestrator instances can assign thinking levels programmatically based on task complexity
|
7
|
+
- Example: Complex architectural decisions can be delegated with "think harder" while simple queries use no thinking
|
8
|
+
- Results in better quality outputs for complex tasks and faster responses for simple ones
|
9
|
+
- Works seamlessly with existing swarm configurations - no changes needed to benefit from this feature
|
10
|
+
|
11
|
+
## [0.3.1]
|
12
|
+
|
13
|
+
### Added
|
14
|
+
- **Interactive mode with initial prompt**: Added `-i/--interactive` flag to provide an initial prompt for interactive mode
|
15
|
+
- Use `claude-swarm -i "Your initial prompt"` to start in interactive mode with a prompt
|
16
|
+
- Cannot be used together with `-p/--prompt` (which is for non-interactive mode)
|
17
|
+
- Allows users to provide context or initial instructions while maintaining interactive session
|
18
|
+
|
19
|
+
### Fixed
|
20
|
+
- **Development documentation**: Fixed `bundle exec` prefix in CLAUDE.md for development commands
|
21
|
+
- **Bundler environment conflicts**: Fixed issue where Claude instances would inherit bundler environment variables, causing conflicts when working in Ruby projects
|
22
|
+
- MCP servers now receive necessary Ruby/Bundler environment variables to run properly
|
23
|
+
- Claude instances (main and connected) run in clean environments via `Bundler.with_unbundled_env`
|
24
|
+
- Prevents `bundle install` and other bundler commands from using Claude Swarm's Gemfile instead of the project's Gemfile
|
25
|
+
- `claude mcp serve` now runs with a filtered environment that excludes Ruby/Bundler variables while preserving system variables
|
26
|
+
|
1
27
|
## [0.3.0]
|
2
28
|
|
3
29
|
### Added
|
data/CLAUDE.md
CHANGED
@@ -8,14 +8,9 @@ Claude Swarm is a Ruby gem that orchestrates multiple Claude Code instances as a
|
|
8
8
|
|
9
9
|
## Development Commands
|
10
10
|
|
11
|
-
### Setup
|
12
|
-
```bash
|
13
|
-
bin/setup # Install dependencies
|
14
|
-
```
|
15
|
-
|
16
11
|
### Testing
|
17
12
|
```bash
|
18
|
-
rake test # Run the Minitest test suite
|
13
|
+
bundle exec rake test # Run the Minitest test suite
|
19
14
|
```
|
20
15
|
|
21
16
|
**Important**: Tests should not generate any output to stdout or stderr. When writing tests:
|
@@ -37,7 +32,7 @@ end
|
|
37
32
|
|
38
33
|
### Linting
|
39
34
|
```bash
|
40
|
-
|
35
|
+
bundle exec rubocop -A # Run RuboCop linter to auto fix problems
|
41
36
|
```
|
42
37
|
|
43
38
|
### Development Console
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Claude Swarm
|
2
2
|
|
3
|
-
[](https://badge.fury.io/rb/claude_swarm)
|
4
4
|
[](https://github.com/parruda/claude-swarm/actions/workflows/ci.yml)
|
5
5
|
|
6
6
|
Claude Swarm orchestrates multiple Claude Code instances as a collaborative AI development team. It enables running AI agents with specialized roles, tools, and directory contexts, communicating via MCP (Model Context Protocol) in a tree-like hierarchy. Define your swarm topology in simple YAML and let Claude instances delegate tasks through connected instances. Perfect for complex projects requiring specialized AI agents for frontend, backend, testing, DevOps, or research tasks.
|
@@ -767,6 +767,10 @@ claude-swarm --vibe
|
|
767
767
|
claude-swarm -p "Implement the new user authentication feature"
|
768
768
|
claude-swarm --prompt "Fix the bug in the payment module"
|
769
769
|
|
770
|
+
# Run in interactive mode with an initial prompt
|
771
|
+
claude-swarm -i "Review the codebase and suggest improvements"
|
772
|
+
claude-swarm --interactive "Help me debug this test failure"
|
773
|
+
|
770
774
|
# Use a custom session ID instead of auto-generated UUID
|
771
775
|
claude-swarm --session-id my-custom-session-123
|
772
776
|
|
@@ -33,43 +33,48 @@ module ClaudeSwarm
|
|
33
33
|
stderr_output = []
|
34
34
|
result_response = nil
|
35
35
|
|
36
|
-
# Execute command with
|
37
|
-
|
38
|
-
|
36
|
+
# Execute command with unbundled environment to avoid bundler conflicts
|
37
|
+
# This ensures claude runs in a clean environment without inheriting
|
38
|
+
# Claude Swarm's BUNDLE_* environment variables
|
39
|
+
Bundler.with_unbundled_env do
|
40
|
+
# Execute command with streaming
|
41
|
+
Open3.popen3(*cmd_array, chdir: @working_directory) do |stdin, stdout, stderr, wait_thread|
|
42
|
+
stdin.close
|
43
|
+
|
44
|
+
# Read stderr in a separate thread
|
45
|
+
stderr_thread = Thread.new do
|
46
|
+
stderr.each_line { |line| stderr_output << line }
|
47
|
+
end
|
39
48
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
49
|
+
# Process stdout line by line
|
50
|
+
stdout.each_line do |line|
|
51
|
+
json_data = JSON.parse(line.strip)
|
44
52
|
|
45
|
-
|
46
|
-
|
47
|
-
json_data = JSON.parse(line.strip)
|
53
|
+
# Log each JSON event
|
54
|
+
log_streaming_event(json_data)
|
48
55
|
|
49
|
-
|
50
|
-
|
56
|
+
# Capture session_id from system init
|
57
|
+
if json_data["type"] == "system" && json_data["subtype"] == "init"
|
58
|
+
@session_id = json_data["session_id"]
|
59
|
+
write_instance_state
|
60
|
+
end
|
51
61
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
62
|
+
# Capture the final result
|
63
|
+
result_response = json_data if json_data["type"] == "result"
|
64
|
+
rescue JSON::ParserError => e
|
65
|
+
@logger.warn("Failed to parse JSON line: #{line.strip} - #{e.message}")
|
56
66
|
end
|
57
67
|
|
58
|
-
#
|
59
|
-
|
60
|
-
rescue JSON::ParserError => e
|
61
|
-
@logger.warn("Failed to parse JSON line: #{line.strip} - #{e.message}")
|
62
|
-
end
|
68
|
+
# Wait for stderr thread to finish
|
69
|
+
stderr_thread.join
|
63
70
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
@logger.error("Execution error for #{@instance_name}: #{error_msg}")
|
72
|
-
raise ExecutionError, "Claude Code execution failed: #{error_msg}"
|
71
|
+
# Check exit status
|
72
|
+
exit_status = wait_thread.value
|
73
|
+
unless exit_status.success?
|
74
|
+
error_msg = stderr_output.join
|
75
|
+
@logger.error("Execution error for #{@instance_name}: #{error_msg}")
|
76
|
+
raise ExecutionError, "Claude Code execution failed: #{error_msg}"
|
77
|
+
end
|
73
78
|
end
|
74
79
|
end
|
75
80
|
|
@@ -63,10 +63,11 @@ module ClaudeSwarm
|
|
63
63
|
)
|
64
64
|
|
65
65
|
# Set dynamic description for TaskTool based on instance config
|
66
|
+
thinking_info = " Thinking budget levels: \"think\" < \"think hard\" < \"think harder\" < \"ultrathink\"."
|
66
67
|
if @instance_config[:description]
|
67
|
-
Tools::TaskTool.description("Execute a task using Agent #{@instance_config[:name]}. #{@instance_config[:description]}")
|
68
|
+
Tools::TaskTool.description("Execute a task using Agent #{@instance_config[:name]}. #{@instance_config[:description]} #{thinking_info}")
|
68
69
|
else
|
69
|
-
Tools::TaskTool.description("Execute a task using Agent #{@instance_config[:name]}")
|
70
|
+
Tools::TaskTool.description("Execute a task using Agent #{@instance_config[:name]}. #{thinking_info}")
|
70
71
|
end
|
71
72
|
|
72
73
|
# Register tool classes (not instances)
|
data/lib/claude_swarm/cli.rb
CHANGED
@@ -18,6 +18,10 @@ module ClaudeSwarm
|
|
18
18
|
aliases: "-p",
|
19
19
|
type: :string,
|
20
20
|
desc: "Prompt to pass to the main Claude instance (non-interactive mode)"
|
21
|
+
method_option :interactive,
|
22
|
+
aliases: "-i",
|
23
|
+
type: :string,
|
24
|
+
desc: "Initial prompt for interactive mode"
|
21
25
|
method_option :stream_logs,
|
22
26
|
type: :boolean,
|
23
27
|
default: false,
|
@@ -56,6 +60,12 @@ module ClaudeSwarm
|
|
56
60
|
exit(1)
|
57
61
|
end
|
58
62
|
|
63
|
+
# Validate conflicting options
|
64
|
+
if options[:prompt] && options[:interactive]
|
65
|
+
error("Cannot use both -p/--prompt and -i/--interactive")
|
66
|
+
exit(1)
|
67
|
+
end
|
68
|
+
|
59
69
|
begin
|
60
70
|
config = Configuration.new(config_path, base_dir: ClaudeSwarm.root_dir, options: options)
|
61
71
|
generator = McpGenerator.new(config, vibe: options[:vibe])
|
@@ -64,6 +74,7 @@ module ClaudeSwarm
|
|
64
74
|
generator,
|
65
75
|
vibe: options[:vibe],
|
66
76
|
prompt: options[:prompt],
|
77
|
+
interactive_prompt: options[:interactive],
|
67
78
|
stream_logs: options[:stream_logs],
|
68
79
|
debug: options[:debug],
|
69
80
|
worktree: options[:worktree],
|
@@ -95,10 +95,21 @@ module ClaudeSwarm
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def build_claude_tools_mcp_config
|
98
|
+
# Build environment for claude mcp serve by excluding Ruby/Bundler-specific variables
|
99
|
+
# This preserves all system variables while removing Ruby contamination
|
100
|
+
clean_env = ENV.to_h.reject do |key, _|
|
101
|
+
key.start_with?("BUNDLE_") ||
|
102
|
+
key.start_with?("RUBY") ||
|
103
|
+
key.start_with?("GEM_") ||
|
104
|
+
key == "RUBYOPT" ||
|
105
|
+
key == "RUBYLIB"
|
106
|
+
end
|
107
|
+
|
98
108
|
{
|
99
109
|
"type" => "stdio",
|
100
110
|
"command" => "claude",
|
101
111
|
"args" => ["mcp", "serve"],
|
112
|
+
"env" => clean_env,
|
102
113
|
}
|
103
114
|
end
|
104
115
|
|
@@ -161,11 +172,45 @@ module ClaudeSwarm
|
|
161
172
|
args.push("--claude-session-id", claude_session_id) if claude_session_id
|
162
173
|
end
|
163
174
|
|
164
|
-
|
175
|
+
# Capture environment variables needed for Ruby and Bundler to work properly
|
176
|
+
# This includes both BUNDLE_* variables and Ruby-specific variables
|
177
|
+
required_env = {}
|
178
|
+
|
179
|
+
# Bundle-specific variables
|
180
|
+
ENV.each do |k, v|
|
181
|
+
required_env[k] = v if k.start_with?("BUNDLE_")
|
182
|
+
end
|
183
|
+
|
184
|
+
# Claude Swarm-specific variables
|
185
|
+
ENV.each do |k, v|
|
186
|
+
required_env[k] = v if k.start_with?("CLAUDE_SWARM_")
|
187
|
+
end
|
188
|
+
|
189
|
+
# Ruby-specific variables that MCP servers need
|
190
|
+
[
|
191
|
+
"RUBY_ROOT",
|
192
|
+
"RUBY_ENGINE",
|
193
|
+
"RUBY_VERSION",
|
194
|
+
"GEM_ROOT",
|
195
|
+
"GEM_HOME",
|
196
|
+
"GEM_PATH",
|
197
|
+
"RUBYOPT",
|
198
|
+
"RUBYLIB",
|
199
|
+
"PATH",
|
200
|
+
].each do |key|
|
201
|
+
required_env[key] = ENV[key] if ENV[key]
|
202
|
+
end
|
203
|
+
|
204
|
+
config = {
|
165
205
|
"type" => "stdio",
|
166
206
|
"command" => exe_path,
|
167
207
|
"args" => args,
|
168
208
|
}
|
209
|
+
|
210
|
+
# Add required environment variables if any exist
|
211
|
+
config["env"] = required_env unless required_env.empty?
|
212
|
+
|
213
|
+
config
|
169
214
|
end
|
170
215
|
|
171
216
|
def load_instance_states
|
@@ -181,18 +181,23 @@ module ClaudeSwarm
|
|
181
181
|
end
|
182
182
|
|
183
183
|
if mcp_configs.any?
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
184
|
+
# Create MCP client with unbundled environment to avoid bundler conflicts
|
185
|
+
# This ensures MCP servers run in a clean environment without inheriting
|
186
|
+
# Claude Swarm's BUNDLE_* environment variables
|
187
|
+
Bundler.with_unbundled_env do
|
188
|
+
@mcp_client = MCPClient.create_client(
|
189
|
+
mcp_server_configs: mcp_configs,
|
190
|
+
logger: @logger,
|
191
|
+
)
|
192
|
+
|
193
|
+
# List available tools from all MCP servers
|
194
|
+
begin
|
195
|
+
@available_tools = @mcp_client.list_tools
|
196
|
+
@logger.info("Loaded #{@available_tools.size} tools from #{mcp_configs.size} MCP server(s)")
|
197
|
+
rescue StandardError => e
|
198
|
+
@logger.error("Failed to load MCP tools: #{e.message}")
|
199
|
+
@available_tools = []
|
200
|
+
end
|
196
201
|
end
|
197
202
|
end
|
198
203
|
end
|
@@ -5,12 +5,13 @@ module ClaudeSwarm
|
|
5
5
|
include SystemUtils
|
6
6
|
RUN_DIR = File.expand_path("~/.claude-swarm/run")
|
7
7
|
|
8
|
-
def initialize(configuration, mcp_generator, vibe: false, prompt: nil, stream_logs: false, debug: false,
|
8
|
+
def initialize(configuration, mcp_generator, vibe: false, prompt: nil, interactive_prompt: nil, stream_logs: false, debug: false,
|
9
9
|
restore_session_path: nil, worktree: nil, session_id: nil)
|
10
10
|
@config = configuration
|
11
11
|
@generator = mcp_generator
|
12
12
|
@vibe = vibe
|
13
|
-
@
|
13
|
+
@non_interactive_prompt = prompt
|
14
|
+
@interactive_prompt = interactive_prompt
|
14
15
|
@stream_logs = stream_logs
|
15
16
|
@debug = debug
|
16
17
|
@restore_session_path = restore_session_path
|
@@ -26,7 +27,7 @@ module ClaudeSwarm
|
|
26
27
|
@start_time = nil
|
27
28
|
|
28
29
|
# Set environment variable for prompt mode to suppress output
|
29
|
-
ENV["CLAUDE_SWARM_PROMPT"] = "1" if @
|
30
|
+
ENV["CLAUDE_SWARM_PROMPT"] = "1" if @non_interactive_prompt
|
30
31
|
end
|
31
32
|
|
32
33
|
def start
|
@@ -34,7 +35,7 @@ module ClaudeSwarm
|
|
34
35
|
@start_time = Time.now
|
35
36
|
|
36
37
|
if @restore_session_path
|
37
|
-
unless @
|
38
|
+
unless @non_interactive_prompt
|
38
39
|
puts "🔄 Restoring Claude Swarm: #{@config.swarm_name}"
|
39
40
|
puts "😎 Vibe mode ON" if @vibe
|
40
41
|
puts
|
@@ -49,7 +50,7 @@ module ClaudeSwarm
|
|
49
50
|
# Create run symlink for restored session
|
50
51
|
create_run_symlink
|
51
52
|
|
52
|
-
unless @
|
53
|
+
unless @non_interactive_prompt
|
53
54
|
puts "📝 Using existing session: #{session_path}/"
|
54
55
|
puts
|
55
56
|
end
|
@@ -65,12 +66,12 @@ module ClaudeSwarm
|
|
65
66
|
|
66
67
|
# Regenerate MCP configurations with session IDs for restoration
|
67
68
|
@generator.generate_all
|
68
|
-
unless @
|
69
|
+
unless @non_interactive_prompt
|
69
70
|
puts "✓ Regenerated MCP configurations with session IDs"
|
70
71
|
puts
|
71
72
|
end
|
72
73
|
else
|
73
|
-
unless @
|
74
|
+
unless @non_interactive_prompt
|
74
75
|
puts "🐝 Starting Claude Swarm: #{@config.swarm_name}"
|
75
76
|
puts "😎 Vibe mode ON" if @vibe
|
76
77
|
puts
|
@@ -94,7 +95,7 @@ module ClaudeSwarm
|
|
94
95
|
# Create run symlink for new session
|
95
96
|
create_run_symlink
|
96
97
|
|
97
|
-
unless @
|
98
|
+
unless @non_interactive_prompt
|
98
99
|
puts "📝 Session files will be saved to: #{session_path}/"
|
99
100
|
puts
|
100
101
|
end
|
@@ -109,7 +110,7 @@ module ClaudeSwarm
|
|
109
110
|
if @needs_worktree_manager
|
110
111
|
cli_option = @worktree_option.is_a?(String) && !@worktree_option.empty? ? @worktree_option : nil
|
111
112
|
@worktree_manager = WorktreeManager.new(cli_option, session_id: @session_id)
|
112
|
-
puts "🌳 Setting up Git worktrees..." unless @
|
113
|
+
puts "🌳 Setting up Git worktrees..." unless @non_interactive_prompt
|
113
114
|
|
114
115
|
# Get all instances for worktree setup
|
115
116
|
# Note: instances.values already includes the main instance
|
@@ -117,7 +118,7 @@ module ClaudeSwarm
|
|
117
118
|
|
118
119
|
@worktree_manager.setup_worktrees(all_instances)
|
119
120
|
|
120
|
-
unless @
|
121
|
+
unless @non_interactive_prompt
|
121
122
|
puts "✓ Worktrees created with branch: #{@worktree_manager.worktree_name}"
|
122
123
|
puts
|
123
124
|
end
|
@@ -125,7 +126,7 @@ module ClaudeSwarm
|
|
125
126
|
|
126
127
|
# Generate all MCP configuration files
|
127
128
|
@generator.generate_all
|
128
|
-
unless @
|
129
|
+
unless @non_interactive_prompt
|
129
130
|
puts "✓ Generated MCP configurations in session directory"
|
130
131
|
puts
|
131
132
|
end
|
@@ -136,7 +137,7 @@ module ClaudeSwarm
|
|
136
137
|
|
137
138
|
# Launch the main instance (fetch after worktree setup to get modified paths)
|
138
139
|
main_instance = @config.main_instance_config
|
139
|
-
unless @
|
140
|
+
unless @non_interactive_prompt
|
140
141
|
puts "🚀 Launching main instance: #{@config.main_instance}"
|
141
142
|
puts " Model: #{main_instance[:model]}"
|
142
143
|
if main_instance[:directories].size == 1
|
@@ -153,14 +154,14 @@ module ClaudeSwarm
|
|
153
154
|
end
|
154
155
|
|
155
156
|
command = build_main_command(main_instance)
|
156
|
-
if @debug && !@
|
157
|
+
if @debug && !@non_interactive_prompt
|
157
158
|
puts "🏃 Running: #{format_command_for_display(command)}"
|
158
159
|
puts
|
159
160
|
end
|
160
161
|
|
161
162
|
# Start log streaming thread if in non-interactive mode with --stream-logs
|
162
163
|
log_thread = nil
|
163
|
-
log_thread = start_log_streaming if @
|
164
|
+
log_thread = start_log_streaming if @non_interactive_prompt && @stream_logs
|
164
165
|
|
165
166
|
# Write the current process PID (orchestrator) to a file for easy access
|
166
167
|
main_pid_file = File.join(@session_path, "main_pid")
|
@@ -171,27 +172,32 @@ module ClaudeSwarm
|
|
171
172
|
# Execute before commands if specified
|
172
173
|
before_commands = @config.before_commands
|
173
174
|
if before_commands.any? && !@restore_session_path
|
174
|
-
unless @
|
175
|
+
unless @non_interactive_prompt
|
175
176
|
puts "⚙️ Executing before commands..."
|
176
177
|
puts
|
177
178
|
end
|
178
179
|
|
179
180
|
success = execute_before_commands?(before_commands)
|
180
181
|
unless success
|
181
|
-
puts "❌ Before commands failed. Aborting swarm launch." unless @
|
182
|
+
puts "❌ Before commands failed. Aborting swarm launch." unless @non_interactive_prompt
|
182
183
|
cleanup_processes
|
183
184
|
cleanup_run_symlink
|
184
185
|
cleanup_worktrees
|
185
186
|
exit(1)
|
186
187
|
end
|
187
188
|
|
188
|
-
unless @
|
189
|
+
unless @non_interactive_prompt
|
189
190
|
puts "✓ Before commands completed successfully"
|
190
191
|
puts
|
191
192
|
end
|
192
193
|
end
|
193
194
|
|
194
|
-
|
195
|
+
# Execute main Claude instance with unbundled environment to avoid bundler conflicts
|
196
|
+
# This ensures the main instance runs in a clean environment without inheriting
|
197
|
+
# Claude Swarm's BUNDLE_* environment variables
|
198
|
+
Bundler.with_unbundled_env do
|
199
|
+
system!(*command)
|
200
|
+
end
|
195
201
|
end
|
196
202
|
|
197
203
|
# Clean up log streaming thread
|
@@ -207,14 +213,14 @@ module ClaudeSwarm
|
|
207
213
|
after_commands = @config.after_commands
|
208
214
|
if after_commands.any? && !@restore_session_path
|
209
215
|
Dir.chdir(main_instance[:directory]) do
|
210
|
-
unless @
|
216
|
+
unless @non_interactive_prompt
|
211
217
|
puts
|
212
218
|
puts "⚙️ Executing after commands..."
|
213
219
|
puts
|
214
220
|
end
|
215
221
|
|
216
222
|
success = execute_after_commands?(after_commands)
|
217
|
-
if !success && !@
|
223
|
+
if !success && !@non_interactive_prompt
|
218
224
|
puts "⚠️ Some after commands failed"
|
219
225
|
puts
|
220
226
|
end
|
@@ -242,7 +248,7 @@ module ClaudeSwarm
|
|
242
248
|
|
243
249
|
# Execute the command and capture output
|
244
250
|
begin
|
245
|
-
puts "Debug: Executing command #{index + 1}/#{commands.size}: #{command}" if @debug && !@
|
251
|
+
puts "Debug: Executing command #{index + 1}/#{commands.size}: #{command}" if @debug && !@non_interactive_prompt
|
246
252
|
|
247
253
|
# Use system with output capture
|
248
254
|
output = %x(#{command} 2>&1)
|
@@ -259,18 +265,18 @@ module ClaudeSwarm
|
|
259
265
|
end
|
260
266
|
|
261
267
|
# Show output if in debug mode or if command failed
|
262
|
-
if (@debug || !success) && !@
|
268
|
+
if (@debug || !success) && !@non_interactive_prompt
|
263
269
|
puts "Command #{index + 1} output:"
|
264
270
|
puts output
|
265
271
|
puts "Exit status: #{$CHILD_STATUS.exitstatus}"
|
266
272
|
end
|
267
273
|
|
268
274
|
unless success
|
269
|
-
puts "❌ Before command #{index + 1} failed: #{command}" unless @
|
275
|
+
puts "❌ Before command #{index + 1} failed: #{command}" unless @non_interactive_prompt
|
270
276
|
return false
|
271
277
|
end
|
272
278
|
rescue StandardError => e
|
273
|
-
puts "Error executing before command #{index + 1}: #{e.message}" unless @
|
279
|
+
puts "Error executing before command #{index + 1}: #{e.message}" unless @non_interactive_prompt
|
274
280
|
if @session_path
|
275
281
|
File.open(log_file, "a") do |f|
|
276
282
|
f.puts "Error: #{e.message}"
|
@@ -298,7 +304,7 @@ module ClaudeSwarm
|
|
298
304
|
|
299
305
|
# Execute the command and capture output
|
300
306
|
begin
|
301
|
-
puts "Debug: Executing after command #{index + 1}/#{commands.size}: #{command}" if @debug && !@
|
307
|
+
puts "Debug: Executing after command #{index + 1}/#{commands.size}: #{command}" if @debug && !@non_interactive_prompt
|
302
308
|
|
303
309
|
# Use system with output capture
|
304
310
|
output = %x(#{command} 2>&1)
|
@@ -315,18 +321,18 @@ module ClaudeSwarm
|
|
315
321
|
end
|
316
322
|
|
317
323
|
# Show output if in debug mode or if command failed
|
318
|
-
if (@debug || !success) && !@
|
324
|
+
if (@debug || !success) && !@non_interactive_prompt
|
319
325
|
puts "After command #{index + 1} output:"
|
320
326
|
puts output
|
321
327
|
puts "Exit status: #{$CHILD_STATUS.exitstatus}"
|
322
328
|
end
|
323
329
|
|
324
330
|
unless success
|
325
|
-
puts "❌ After command #{index + 1} failed: #{command}" unless @
|
331
|
+
puts "❌ After command #{index + 1} failed: #{command}" unless @non_interactive_prompt
|
326
332
|
all_succeeded = false
|
327
333
|
end
|
328
334
|
rescue StandardError => e
|
329
|
-
puts "Error executing after command #{index + 1}: #{e.message}" unless @
|
335
|
+
puts "Error executing after command #{index + 1}: #{e.message}" unless @non_interactive_prompt
|
330
336
|
if @session_path
|
331
337
|
File.open(log_file, "a") do |f|
|
332
338
|
f.puts "Error: #{e.message}"
|
@@ -374,7 +380,7 @@ module ClaudeSwarm
|
|
374
380
|
# Execute after commands if configured
|
375
381
|
main_instance = @config.main_instance_config
|
376
382
|
after_commands = @config.after_commands
|
377
|
-
if after_commands.any? && !@restore_session_path && !@
|
383
|
+
if after_commands.any? && !@restore_session_path && !@non_interactive_prompt
|
378
384
|
Dir.chdir(main_instance[:directory]) do
|
379
385
|
puts
|
380
386
|
puts "⚙️ Executing after commands..."
|
@@ -438,7 +444,7 @@ module ClaudeSwarm
|
|
438
444
|
|
439
445
|
File.write(metadata_file, JSON.pretty_generate(metadata))
|
440
446
|
rescue StandardError => e
|
441
|
-
puts "⚠️ Error updating session metadata: #{e.message}" unless @
|
447
|
+
puts "⚠️ Error updating session metadata: #{e.message}" unless @non_interactive_prompt
|
442
448
|
end
|
443
449
|
|
444
450
|
def calculate_total_cost
|
@@ -487,7 +493,7 @@ module ClaudeSwarm
|
|
487
493
|
File.symlink(@session_path, symlink_path)
|
488
494
|
rescue StandardError => e
|
489
495
|
# Don't fail the process if symlink creation fails
|
490
|
-
puts "⚠️ Warning: Could not create run symlink: #{e.message}" unless @
|
496
|
+
puts "⚠️ Warning: Could not create run symlink: #{e.message}" unless @non_interactive_prompt
|
491
497
|
end
|
492
498
|
|
493
499
|
def cleanup_run_symlink
|
@@ -589,6 +595,7 @@ module ClaudeSwarm
|
|
589
595
|
end
|
590
596
|
end
|
591
597
|
|
598
|
+
# Always add instance prompt if it exists
|
592
599
|
if instance[:prompt]
|
593
600
|
parts << "--append-system-prompt"
|
594
601
|
parts << instance[:prompt]
|
@@ -608,12 +615,18 @@ module ClaudeSwarm
|
|
608
615
|
parts << "--mcp-config"
|
609
616
|
parts << mcp_config_path
|
610
617
|
|
611
|
-
|
618
|
+
# Handle different modes
|
619
|
+
if @non_interactive_prompt
|
620
|
+
# Non-interactive mode with -p
|
612
621
|
parts << "-p"
|
613
|
-
parts << @
|
614
|
-
|
615
|
-
|
622
|
+
parts << @non_interactive_prompt
|
623
|
+
elsif @interactive_prompt
|
624
|
+
# Interactive mode with initial prompt (no -p flag)
|
625
|
+
parts << @interactive_prompt
|
616
626
|
end
|
627
|
+
# else: Interactive mode without initial prompt - nothing to add
|
628
|
+
|
629
|
+
parts
|
617
630
|
end
|
618
631
|
|
619
632
|
def restore_worktrees_if_needed(session_path)
|
@@ -624,7 +637,7 @@ module ClaudeSwarm
|
|
624
637
|
worktree_data = metadata["worktree"]
|
625
638
|
return unless worktree_data && worktree_data["enabled"]
|
626
639
|
|
627
|
-
unless @
|
640
|
+
unless @non_interactive_prompt
|
628
641
|
puts "🌳 Restoring Git worktrees..."
|
629
642
|
puts
|
630
643
|
end
|
@@ -638,7 +651,7 @@ module ClaudeSwarm
|
|
638
651
|
all_instances = @config.instances.values
|
639
652
|
@worktree_manager.setup_worktrees(all_instances)
|
640
653
|
|
641
|
-
return if @
|
654
|
+
return if @non_interactive_prompt
|
642
655
|
|
643
656
|
puts "✓ Worktrees restored with branch: #{@worktree_manager.worktree_name}"
|
644
657
|
puts
|
@@ -12,12 +12,20 @@ module ClaudeSwarm
|
|
12
12
|
optional(:new_session).filled(:bool).description("Start a new session (default: false)")
|
13
13
|
optional(:system_prompt).filled(:string).description("Override the system prompt for this request")
|
14
14
|
optional(:description).filled(:string).description("A description for the request")
|
15
|
+
optional(:thinking_budget).filled(:string).description("Thinking budget: \"think\" < \"think hard\" < \"think harder\" < \"ultrathink\". Each level increases Claude's thinking allocation. Auto-select based on task complexity.")
|
15
16
|
end
|
16
17
|
|
17
|
-
def call(prompt:, new_session: false, system_prompt: nil, description: nil)
|
18
|
+
def call(prompt:, new_session: false, system_prompt: nil, description: nil, thinking_budget: nil)
|
18
19
|
executor = ClaudeMcpServer.executor
|
19
20
|
instance_config = ClaudeMcpServer.instance_config
|
20
21
|
|
22
|
+
# Prepend thinking budget to prompt if provided
|
23
|
+
final_prompt = if thinking_budget
|
24
|
+
"#{thinking_budget}: #{prompt}"
|
25
|
+
else
|
26
|
+
prompt
|
27
|
+
end
|
28
|
+
|
21
29
|
options = {
|
22
30
|
new_session: new_session,
|
23
31
|
system_prompt: system_prompt || instance_config[:prompt],
|
@@ -33,7 +41,7 @@ module ClaudeSwarm
|
|
33
41
|
# Add connections from instance config
|
34
42
|
options[:connections] = instance_config[:connections] if instance_config[:connections]&.any?
|
35
43
|
|
36
|
-
response = executor.execute(
|
44
|
+
response = executor.execute(final_prompt, options)
|
37
45
|
|
38
46
|
# Return just the result text as expected by MCP
|
39
47
|
response["result"]
|
data/lib/claude_swarm/version.rb
CHANGED