claude_swarm 0.3.7 → 0.3.9
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 +35 -0
- data/CLAUDE.md +50 -3
- data/README.md +64 -0
- data/examples/simple-session-hook-swarm.yml +37 -0
- data/lib/claude_swarm/claude_code_executor.rb +4 -0
- data/lib/claude_swarm/cli.rb +6 -1
- data/lib/claude_swarm/configuration.rb +1 -0
- data/lib/claude_swarm/hooks/session_start_hook.rb +42 -0
- data/lib/claude_swarm/openai/executor.rb +120 -82
- data/lib/claude_swarm/orchestrator.rb +144 -37
- data/lib/claude_swarm/settings_generator.rb +77 -0
- data/lib/claude_swarm/templates/generation_prompt.md.erb +4 -7
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm.rb +0 -2
- metadata +18 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55711fd9483c68d996c0a0e448c4d31135d7423269d1ed691bb3590db5487627
|
4
|
+
data.tar.gz: a84c0b4e1165057cbbe22bcd4d6c02c8fcdaedb5abb2f18e150b667ee0083385
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc2808da58a6cb2b2ac545cfbe4803382602d4339017337d3af84b92274baab241952186f7de4e72adfbd413d0fcb448b955fb4cb7ebb5ebc014b90895988247
|
7
|
+
data.tar.gz: 2d58c3795f4ea80da550c6241ad7639ffa5d25fa868eb9af18bf58382961f8cad4fb930616c133197858193fb2f9627aead812bfd60a3565631ef38398351b16
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
## [0.3.9]
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- **Main instance transcript integration**: Main Claude instance activity is now captured in session.log.json during interactive mode
|
5
|
+
- Automatically configures SessionStart hook for main instance to capture transcript path
|
6
|
+
- Background thread continuously tails transcript file and integrates entries into session.log.json
|
7
|
+
- Filters out summary entries to avoid duplicate conversation titles
|
8
|
+
- Uses file locking for thread-safe writes to maintain consistency
|
9
|
+
- Provides complete session history including main instance interactions
|
10
|
+
|
11
|
+
## [0.3.8]
|
12
|
+
|
13
|
+
### Added
|
14
|
+
- **Hooks support**: Claude Swarm now supports configuring Claude Code hooks for each instance
|
15
|
+
- Configure hooks directly in the YAML configuration file using Claude Code's format
|
16
|
+
- Each instance can have its own hooks configuration (PreToolUse, PostToolUse, UserPromptSubmit, etc.)
|
17
|
+
- Automatically generates `settings.json` files in the session directory when hooks are configured
|
18
|
+
- Main instance receives hooks via `--settings` CLI flag
|
19
|
+
- Connected instances receive hooks via SDK's `settings` attribute
|
20
|
+
- Full environment variable interpolation support in hook configurations
|
21
|
+
- See README.md "Hooks Configuration" section for usage examples
|
22
|
+
- **Persistent HTTP connections for OpenAI**: Added `faraday-net_http_persistent` dependency and configured OpenAI client to use persistent connections
|
23
|
+
- Improves performance when making multiple API requests by reusing HTTP connections
|
24
|
+
- Automatically configured for all OpenAI instances
|
25
|
+
|
26
|
+
### Changed
|
27
|
+
- **Improved OpenAI executor code organization**: Refactored internal methods for better maintainability
|
28
|
+
- Extracted configuration building and response handling into focused private methods
|
29
|
+
- Improved code readability with functional patterns
|
30
|
+
|
31
|
+
### Fixed
|
32
|
+
- **Settings integration**: Fixed passing settings to Claude instances
|
33
|
+
- Corrected SDK attribute name from `settings_path` to `settings`
|
34
|
+
- Added missing `--settings` flag for main instance CLI command
|
35
|
+
|
1
36
|
## [0.3.7]
|
2
37
|
|
3
38
|
### Added
|
data/CLAUDE.md
CHANGED
@@ -130,9 +130,10 @@ The gem is fully implemented with the following components:
|
|
130
130
|
1. User creates a `claude-swarm.yml` file defining the swarm topology
|
131
131
|
2. Running `claude-swarm` parses the configuration and validates it
|
132
132
|
3. MCP configuration files are generated for each instance in a session directory
|
133
|
-
4.
|
134
|
-
5.
|
135
|
-
6.
|
133
|
+
4. Settings files (with hooks) are generated for each instance if hooks are configured
|
134
|
+
5. The main instance is launched with `exec`, replacing the current process
|
135
|
+
6. Connected instances are available as MCP servers to the main instance
|
136
|
+
7. When an instance has connections, those connections are automatically added to its allowed tools as `mcp__<connection_name>`
|
136
137
|
|
137
138
|
### Configuration Example
|
138
139
|
|
@@ -159,6 +160,52 @@ swarm:
|
|
159
160
|
worktree: false # Optional: disable worktree for this instance
|
160
161
|
```
|
161
162
|
|
163
|
+
### Hooks Support
|
164
|
+
|
165
|
+
Claude Swarm supports configuring [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) for each instance. This allows you to run custom scripts before/after tools, on prompt submission, and more.
|
166
|
+
|
167
|
+
#### Configuration Example with Hooks
|
168
|
+
|
169
|
+
```yaml
|
170
|
+
version: 1
|
171
|
+
swarm:
|
172
|
+
name: "Dev Team"
|
173
|
+
main: lead
|
174
|
+
instances:
|
175
|
+
lead:
|
176
|
+
description: "Lead developer"
|
177
|
+
directory: .
|
178
|
+
model: opus
|
179
|
+
# Hooks configuration follows Claude Code's format exactly
|
180
|
+
hooks:
|
181
|
+
PreToolUse:
|
182
|
+
- matcher: "Write|Edit"
|
183
|
+
hooks:
|
184
|
+
- type: "command"
|
185
|
+
command: "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-code.py"
|
186
|
+
timeout: 10
|
187
|
+
PostToolUse:
|
188
|
+
- matcher: "Bash"
|
189
|
+
hooks:
|
190
|
+
- type: "command"
|
191
|
+
command: "echo 'Command executed by lead' >> /tmp/lead.log"
|
192
|
+
UserPromptSubmit:
|
193
|
+
- hooks:
|
194
|
+
- type: "command"
|
195
|
+
command: "$CLAUDE_PROJECT_DIR/.claude/hooks/add-context.py"
|
196
|
+
frontend:
|
197
|
+
description: "Frontend developer"
|
198
|
+
directory: ./frontend
|
199
|
+
hooks:
|
200
|
+
PreToolUse:
|
201
|
+
- matcher: "Write"
|
202
|
+
hooks:
|
203
|
+
- type: "command"
|
204
|
+
command: "npm run lint"
|
205
|
+
```
|
206
|
+
|
207
|
+
The hooks configuration is passed directly to Claude Code via a generated settings.json file in the session directory. Each instance gets its own settings file with its specific hooks.
|
208
|
+
|
162
209
|
## Testing
|
163
210
|
|
164
211
|
The gem includes comprehensive tests covering:
|
data/README.md
CHANGED
@@ -303,6 +303,7 @@ Each instance can have:
|
|
303
303
|
- **vibe**: Enable vibe mode (--dangerously-skip-permissions) for this instance (default: false)
|
304
304
|
- **worktree**: Configure Git worktree usage for this instance (true/false/string)
|
305
305
|
- **provider**: AI provider to use - "claude" (default) or "openai"
|
306
|
+
- **hooks**: Configure Claude Code hooks for this instance (see Hooks Configuration section below)
|
306
307
|
|
307
308
|
#### OpenAI Provider Configuration
|
308
309
|
|
@@ -394,6 +395,69 @@ mcps:
|
|
394
395
|
X-Custom-Header: "value"
|
395
396
|
```
|
396
397
|
|
398
|
+
### Hooks Configuration
|
399
|
+
|
400
|
+
Claude Swarm supports configuring [Claude Code hooks](https://docs.anthropic.com/en/docs/claude-code/hooks) for each instance. Hooks allow you to run custom scripts before/after tools, on prompt submission, and more. Each instance can have its own hooks configuration.
|
401
|
+
|
402
|
+
#### Supported Hook Events
|
403
|
+
|
404
|
+
- **PreToolUse**: Run before a tool is executed
|
405
|
+
- **PostToolUse**: Run after a tool completes
|
406
|
+
- **UserPromptSubmit**: Run when a user prompt is submitted
|
407
|
+
- **Stop**: Run when the Claude instance stops
|
408
|
+
- **SessionStart**: Run when a session starts
|
409
|
+
- **And more...** (see Claude Code hooks documentation)
|
410
|
+
|
411
|
+
#### Configuration Example
|
412
|
+
|
413
|
+
```yaml
|
414
|
+
instances:
|
415
|
+
lead:
|
416
|
+
description: "Lead developer"
|
417
|
+
directory: .
|
418
|
+
# Hooks configuration follows Claude Code's format exactly
|
419
|
+
hooks:
|
420
|
+
PreToolUse:
|
421
|
+
- matcher: "Write|Edit"
|
422
|
+
hooks:
|
423
|
+
- type: "command"
|
424
|
+
command: "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-code.py"
|
425
|
+
timeout: 10
|
426
|
+
PostToolUse:
|
427
|
+
- matcher: "Bash"
|
428
|
+
hooks:
|
429
|
+
- type: "command"
|
430
|
+
command: "echo 'Bash executed by ${INSTANCE_NAME}' >> ${LOG_DIR}/commands.log"
|
431
|
+
UserPromptSubmit:
|
432
|
+
- hooks:
|
433
|
+
- type: "command"
|
434
|
+
command: "${HOOKS_DIR:=$CLAUDE_PROJECT_DIR/.claude/hooks}/add-context.py"
|
435
|
+
frontend:
|
436
|
+
description: "Frontend developer"
|
437
|
+
directory: ./frontend
|
438
|
+
hooks:
|
439
|
+
PreToolUse:
|
440
|
+
- matcher: "Write"
|
441
|
+
hooks:
|
442
|
+
- type: "command"
|
443
|
+
command: "npm run lint"
|
444
|
+
timeout: 5
|
445
|
+
```
|
446
|
+
|
447
|
+
#### How It Works
|
448
|
+
|
449
|
+
1. Define hooks in your instance configuration using the exact format expected by Claude Code
|
450
|
+
2. Claude Swarm generates a `settings.json` file for each instance with hooks
|
451
|
+
3. The settings file is passed to Claude Code SDK via the `--settings` parameter
|
452
|
+
4. Each instance runs with its own hooks configuration
|
453
|
+
|
454
|
+
#### Environment Variables in Hooks
|
455
|
+
|
456
|
+
Hooks have access to standard Claude Code environment variables plus:
|
457
|
+
- `$CLAUDE_PROJECT_DIR` - The project directory
|
458
|
+
- `$CLAUDE_SWARM_SESSION_DIR` - The swarm session directory
|
459
|
+
- `$CLAUDE_SWARM_INSTANCE_NAME` - The name of the current instance
|
460
|
+
|
397
461
|
### Tools
|
398
462
|
|
399
463
|
Specify which tools each instance can use:
|
@@ -0,0 +1,37 @@
|
|
1
|
+
version: 1
|
2
|
+
swarm:
|
3
|
+
name: "Simple Session Hook Swarm"
|
4
|
+
main: developer
|
5
|
+
instances:
|
6
|
+
developer:
|
7
|
+
description: "Main developer instance"
|
8
|
+
directory: .
|
9
|
+
model: sonnet
|
10
|
+
allowed_tools:
|
11
|
+
- Read
|
12
|
+
- Edit
|
13
|
+
- Write
|
14
|
+
- Bash
|
15
|
+
connections: [session_tracker]
|
16
|
+
prompt: |
|
17
|
+
You are the main developer. You can delegate session tracking tasks to the session_tracker instance.
|
18
|
+
|
19
|
+
For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
|
20
|
+
|
21
|
+
session_tracker:
|
22
|
+
description: "Session tracking specialist that monitors and logs session information"
|
23
|
+
directory: .
|
24
|
+
model: sonnet
|
25
|
+
allowed_tools:
|
26
|
+
- Write
|
27
|
+
- Bash
|
28
|
+
hooks:
|
29
|
+
SessionStart:
|
30
|
+
- hooks:
|
31
|
+
- type: "command"
|
32
|
+
command: "cat > session_id.txt"
|
33
|
+
timeout: 5
|
34
|
+
prompt: |
|
35
|
+
You specialize in session tracking and monitoring. You automatically create session tracking files when you start.
|
36
|
+
|
37
|
+
For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
|
@@ -184,6 +184,10 @@ module ClaudeSwarm
|
|
184
184
|
setup_additional_directories_mcp(sdk_options)
|
185
185
|
end
|
186
186
|
|
187
|
+
# Add settings file path if it exists
|
188
|
+
settings_file = File.join(@session_path, "#{@instance_name}_settings.json")
|
189
|
+
sdk_options.settings = settings_file if File.exist?(settings_file)
|
190
|
+
|
187
191
|
sdk_options
|
188
192
|
end
|
189
193
|
|
data/lib/claude_swarm/cli.rb
CHANGED
@@ -693,7 +693,12 @@ module ClaudeSwarm
|
|
693
693
|
def build_generation_prompt(readme_content, output_file)
|
694
694
|
template_path = File.expand_path("templates/generation_prompt.md.erb", __dir__)
|
695
695
|
template = File.read(template_path)
|
696
|
-
|
696
|
+
<<~PROMPT
|
697
|
+
#{ERB.new(template, trim_mode: "-").result(binding)}
|
698
|
+
|
699
|
+
Start the conversation by greeting the user and asking: 'What kind of project would you like to create a Claude Swarm for?'
|
700
|
+
Say: 'I am ready to start'
|
701
|
+
PROMPT
|
697
702
|
end
|
698
703
|
end
|
699
704
|
end
|
@@ -200,6 +200,7 @@ module ClaudeSwarm
|
|
200
200
|
vibe: config["vibe"],
|
201
201
|
worktree: parse_worktree_value(config["worktree"]),
|
202
202
|
provider: provider, # nil means Claude (default)
|
203
|
+
hooks: config["hooks"], # Pass hooks configuration as-is
|
203
204
|
}
|
204
205
|
|
205
206
|
# Add OpenAI-specific fields only when provider is "openai"
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# This hook is called when Claude Code starts a session
|
5
|
+
# It saves the transcript path for the main instance so the orchestrator can tail it
|
6
|
+
|
7
|
+
require "json"
|
8
|
+
require "fileutils"
|
9
|
+
|
10
|
+
# Read input from stdin
|
11
|
+
begin
|
12
|
+
stdin_data = $stdin.read
|
13
|
+
input = JSON.parse(stdin_data)
|
14
|
+
rescue => e
|
15
|
+
# Return error response
|
16
|
+
puts JSON.generate({
|
17
|
+
"success" => false,
|
18
|
+
"error" => "Failed to read/parse input: #{e.message}",
|
19
|
+
})
|
20
|
+
exit(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Get session path from command-line argument or environment
|
24
|
+
session_path = ARGV[0] || ENV["CLAUDE_SWARM_SESSION_PATH"]
|
25
|
+
|
26
|
+
if session_path && input["transcript_path"]
|
27
|
+
# Write the transcript path to a known location
|
28
|
+
path_file = File.join(session_path, "main_instance_transcript.path")
|
29
|
+
File.write(path_file, input["transcript_path"])
|
30
|
+
|
31
|
+
# Return success
|
32
|
+
puts JSON.generate({
|
33
|
+
"success" => true,
|
34
|
+
})
|
35
|
+
else
|
36
|
+
# Return error if missing required data
|
37
|
+
puts JSON.generate({
|
38
|
+
"success" => false,
|
39
|
+
"error" => "Missing session path or transcript path",
|
40
|
+
})
|
41
|
+
exit(1)
|
42
|
+
end
|
@@ -1,8 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "openai"
|
4
|
+
require "faraday/net_http_persistent"
|
5
|
+
require "faraday/retry"
|
6
|
+
|
3
7
|
module ClaudeSwarm
|
4
8
|
module OpenAI
|
5
9
|
class Executor < BaseExecutor
|
10
|
+
# Static configuration for Faraday retry middleware
|
11
|
+
FARADAY_RETRY_CONFIG = {
|
12
|
+
max: 3, # Maximum number of retries
|
13
|
+
interval: 0.5, # Initial delay between retries (in seconds)
|
14
|
+
interval_randomness: 0.5, # Randomness factor for retry intervals
|
15
|
+
backoff_factor: 2, # Exponential backoff factor
|
16
|
+
exceptions: [
|
17
|
+
Faraday::TimeoutError,
|
18
|
+
Faraday::ConnectionFailed,
|
19
|
+
Faraday::ServerError, # Retry on 5xx errors
|
20
|
+
].freeze,
|
21
|
+
retry_statuses: [429, 500, 502, 503, 504].freeze, # HTTP status codes to retry
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
# Static configuration for OpenAI client
|
25
|
+
OPENAI_CLIENT_CONFIG = {
|
26
|
+
log_errors: true,
|
27
|
+
request_timeout: 1800, # 30 minutes
|
28
|
+
}.freeze
|
29
|
+
|
6
30
|
def initialize(working_directory: Dir.pwd, model: nil, mcp_config: nil, vibe: false,
|
7
31
|
instance_name: nil, instance_id: nil, calling_instance: nil, calling_instance_id: nil,
|
8
32
|
claude_session_id: nil, additional_directories: [], debug: false,
|
@@ -56,19 +80,8 @@ module ClaudeSwarm
|
|
56
80
|
# Calculate duration
|
57
81
|
duration_ms = ((Time.now - start_time) * 1000).round
|
58
82
|
|
59
|
-
#
|
60
|
-
|
61
|
-
"type" => "result",
|
62
|
-
"result" => result,
|
63
|
-
"duration_ms" => duration_ms,
|
64
|
-
"total_cost" => calculate_cost(result),
|
65
|
-
"session_id" => @session_id,
|
66
|
-
}
|
67
|
-
|
68
|
-
log_response(response)
|
69
|
-
|
70
|
-
@last_response = response
|
71
|
-
response
|
83
|
+
# Build and return response
|
84
|
+
build_response(result, duration_ms)
|
72
85
|
rescue StandardError => e
|
73
86
|
logger.error { "Unexpected error for #{@instance_name}: #{e.class} - #{e.message}" }
|
74
87
|
logger.error { "Backtrace: #{e.backtrace.join("\n")}" }
|
@@ -92,35 +105,14 @@ module ClaudeSwarm
|
|
92
105
|
private
|
93
106
|
|
94
107
|
def setup_openai_client(token_env)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
config[:uri_base] = @base_url if @base_url
|
108
|
+
openai_client_config = build_openai_client_config(token_env)
|
109
|
+
|
110
|
+
@openai_client = ::OpenAI::Client.new(openai_client_config) do |faraday|
|
111
|
+
# Use persistent HTTP connections for better performance
|
112
|
+
faraday.adapter(:net_http_persistent)
|
101
113
|
|
102
|
-
@openai_client = ::OpenAI::Client.new(config) do |faraday|
|
103
114
|
# Add retry middleware with custom configuration
|
104
|
-
faraday.request(
|
105
|
-
:retry,
|
106
|
-
max: 3, # Maximum number of retries
|
107
|
-
interval: 0.5, # Initial delay between retries (in seconds)
|
108
|
-
interval_randomness: 0.5, # Randomness factor for retry intervals
|
109
|
-
backoff_factor: 2, # Exponential backoff factor
|
110
|
-
exceptions: [
|
111
|
-
Faraday::TimeoutError,
|
112
|
-
Faraday::ConnectionFailed,
|
113
|
-
Faraday::ServerError, # Retry on 5xx errors
|
114
|
-
],
|
115
|
-
retry_statuses: [429, 500, 502, 503, 504], # HTTP status codes to retry
|
116
|
-
retry_block: lambda do |env:, options:, retry_count:, exception:, will_retry:|
|
117
|
-
if will_retry
|
118
|
-
@logger.warn("Request failed (attempt #{retry_count}/#{options.max}): #{exception&.message || "HTTP #{env.status}"}. Retrying in #{options.interval * (options.backoff_factor**(retry_count - 1))} seconds...")
|
119
|
-
else
|
120
|
-
@logger.warn("Request failed after #{retry_count} attempts: #{exception&.message || "HTTP #{env.status}"}. Giving up.")
|
121
|
-
end
|
122
|
-
end,
|
123
|
-
)
|
115
|
+
faraday.request(:retry, **build_faraday_retry_config)
|
124
116
|
end
|
125
117
|
rescue KeyError
|
126
118
|
raise ExecutionError, "OpenAI API key not found in environment variable: #{token_env}"
|
@@ -132,49 +124,21 @@ module ClaudeSwarm
|
|
132
124
|
# Read MCP config to find MCP servers
|
133
125
|
mcp_data = JSON.parse(File.read(@mcp_config))
|
134
126
|
|
135
|
-
#
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
command: command_array,
|
148
|
-
name: name,
|
149
|
-
)
|
150
|
-
stdio_config[:read_timeout] = 1800
|
151
|
-
mcp_configs << stdio_config
|
152
|
-
when "sse"
|
153
|
-
logger.warn { "SSE MCP servers not yet supported for OpenAI instances: #{name}" }
|
154
|
-
# TODO: Add SSE support when available in ruby-mcp-client
|
155
|
-
end
|
156
|
-
end
|
127
|
+
# Build MCP configurations from servers
|
128
|
+
mcp_configs = build_mcp_configs(mcp_data["mcpServers"])
|
129
|
+
return if mcp_configs.empty?
|
130
|
+
|
131
|
+
# Create MCP client with unbundled environment to avoid bundler conflicts
|
132
|
+
# This ensures MCP servers run in a clean environment without inheriting
|
133
|
+
# Claude Swarm's BUNDLE_* environment variables
|
134
|
+
Bundler.with_unbundled_env do
|
135
|
+
@mcp_client = MCPClient.create_client(
|
136
|
+
mcp_server_configs: mcp_configs,
|
137
|
+
logger: @logger,
|
138
|
+
)
|
157
139
|
|
158
|
-
|
159
|
-
|
160
|
-
# This ensures MCP servers run in a clean environment without inheriting
|
161
|
-
# Claude Swarm's BUNDLE_* environment variables
|
162
|
-
Bundler.with_unbundled_env do
|
163
|
-
@mcp_client = MCPClient.create_client(
|
164
|
-
mcp_server_configs: mcp_configs,
|
165
|
-
logger: @logger,
|
166
|
-
)
|
167
|
-
|
168
|
-
# List available tools from all MCP servers
|
169
|
-
begin
|
170
|
-
@available_tools = @mcp_client.list_tools
|
171
|
-
logger.info { "Loaded #{@available_tools.size} tools from #{mcp_configs.size} MCP server(s)" }
|
172
|
-
rescue StandardError => e
|
173
|
-
logger.error { "Failed to load MCP tools: #{e.message}" }
|
174
|
-
@available_tools = []
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
140
|
+
# List available tools from all MCP servers
|
141
|
+
load_mcp_tools(mcp_configs)
|
178
142
|
end
|
179
143
|
rescue StandardError => e
|
180
144
|
logger.error { "Failed to setup MCP client: #{e.message}" }
|
@@ -211,6 +175,80 @@ module ClaudeSwarm
|
|
211
175
|
# Log streaming content similar to ClaudeCodeExecutor
|
212
176
|
logger.debug { "#{instance_info} streaming: #{content}" }
|
213
177
|
end
|
178
|
+
|
179
|
+
def build_faraday_retry_config
|
180
|
+
FARADAY_RETRY_CONFIG.merge(
|
181
|
+
retry_block: method(:handle_retry_logging),
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
def handle_retry_logging(env:, options:, retry_count:, exception:, will_retry:)
|
186
|
+
retry_delay = options.interval * (options.backoff_factor**(retry_count - 1))
|
187
|
+
error_info = exception&.message || "HTTP #{env.status}"
|
188
|
+
|
189
|
+
message = if will_retry
|
190
|
+
"Request failed (attempt #{retry_count}/#{options.max}): #{error_info}. Retrying in #{retry_delay} seconds..."
|
191
|
+
else
|
192
|
+
"Request failed after #{retry_count} attempts: #{error_info}. Giving up."
|
193
|
+
end
|
194
|
+
|
195
|
+
@logger.warn(message)
|
196
|
+
end
|
197
|
+
|
198
|
+
def build_openai_client_config(token_env)
|
199
|
+
OPENAI_CLIENT_CONFIG.merge(access_token: ENV.fetch(token_env)).tap do |config|
|
200
|
+
config[:uri_base] = @base_url if @base_url
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def build_stdio_config(name, server_config)
|
205
|
+
# Combine command and args into a single array
|
206
|
+
command_array = [server_config["command"]]
|
207
|
+
command_array.concat(server_config["args"] || [])
|
208
|
+
|
209
|
+
MCPClient.stdio_config(
|
210
|
+
command: command_array,
|
211
|
+
name: name,
|
212
|
+
).tap do |config|
|
213
|
+
config[:read_timeout] = 1800
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def build_mcp_configs(mcp_servers)
|
218
|
+
return [] if mcp_servers.nil? || mcp_servers.empty?
|
219
|
+
|
220
|
+
mcp_servers.filter_map do |name, server_config|
|
221
|
+
case server_config["type"]
|
222
|
+
when "stdio"
|
223
|
+
build_stdio_config(name, server_config)
|
224
|
+
when "sse"
|
225
|
+
logger.warn { "SSE MCP servers not yet supported for OpenAI instances: #{name}" }
|
226
|
+
# TODO: Add SSE support when available in ruby-mcp-client
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def load_mcp_tools(mcp_configs)
|
233
|
+
@available_tools = @mcp_client.list_tools
|
234
|
+
logger.info { "Loaded #{@available_tools.size} tools from #{mcp_configs.size} MCP server(s)" }
|
235
|
+
rescue StandardError => e
|
236
|
+
logger.error { "Failed to load MCP tools: #{e.message}" }
|
237
|
+
@available_tools = []
|
238
|
+
end
|
239
|
+
|
240
|
+
def build_response(result, duration_ms)
|
241
|
+
{
|
242
|
+
"type" => "result",
|
243
|
+
"result" => result,
|
244
|
+
"duration_ms" => duration_ms,
|
245
|
+
"total_cost" => calculate_cost(result),
|
246
|
+
"session_id" => @session_id,
|
247
|
+
}.tap do |response|
|
248
|
+
log_response(response)
|
249
|
+
@last_response = response
|
250
|
+
end
|
251
|
+
end
|
214
252
|
end
|
215
253
|
end
|
216
254
|
end
|
@@ -23,8 +23,6 @@ module ClaudeSwarm
|
|
23
23
|
@stream_logs = stream_logs
|
24
24
|
@debug = debug
|
25
25
|
@restore_session_path = restore_session_path
|
26
|
-
@session_path = nil
|
27
|
-
@session_log_path = nil
|
28
26
|
@provided_session_id = session_id
|
29
27
|
# Store worktree option for later use
|
30
28
|
@worktree_option = worktree
|
@@ -34,9 +32,41 @@ module ClaudeSwarm
|
|
34
32
|
@modified_instances = nil
|
35
33
|
# Track start time for runtime calculation
|
36
34
|
@start_time = nil
|
35
|
+
# Track transcript tailing thread
|
36
|
+
@transcript_thread = nil
|
37
37
|
|
38
38
|
# Set environment variable for prompt mode to suppress output
|
39
39
|
ENV["CLAUDE_SWARM_PROMPT"] = "1" if @non_interactive_prompt
|
40
|
+
|
41
|
+
# Initialize session path
|
42
|
+
if @restore_session_path
|
43
|
+
# Use existing session path for restoration
|
44
|
+
@session_path = @restore_session_path
|
45
|
+
@session_log_path = File.join(@session_path, "session.log")
|
46
|
+
else
|
47
|
+
# Generate new session path
|
48
|
+
session_params = { working_dir: ClaudeSwarm.root_dir }
|
49
|
+
session_params[:session_id] = @provided_session_id if @provided_session_id
|
50
|
+
@session_path = SessionPath.generate(**session_params)
|
51
|
+
SessionPath.ensure_directory(@session_path)
|
52
|
+
@session_log_path = File.join(@session_path, "session.log")
|
53
|
+
|
54
|
+
# Extract session ID from path (the timestamp part)
|
55
|
+
@session_id = File.basename(@session_path)
|
56
|
+
|
57
|
+
end
|
58
|
+
ENV["CLAUDE_SWARM_SESSION_PATH"] = @session_path
|
59
|
+
ENV["CLAUDE_SWARM_ROOT_DIR"] = ClaudeSwarm.root_dir
|
60
|
+
|
61
|
+
# Initialize components that depend on session path
|
62
|
+
@process_tracker = ProcessTracker.new(@session_path)
|
63
|
+
@settings_generator = SettingsGenerator.new(@config)
|
64
|
+
|
65
|
+
# Initialize WorktreeManager if needed
|
66
|
+
if @needs_worktree_manager && !@restore_session_path
|
67
|
+
cli_option = @worktree_option.is_a?(String) && !@worktree_option.empty? ? @worktree_option : nil
|
68
|
+
@worktree_manager = WorktreeManager.new(cli_option, session_id: @session_id)
|
69
|
+
end
|
40
70
|
end
|
41
71
|
|
42
72
|
def start
|
@@ -49,65 +79,42 @@ module ClaudeSwarm
|
|
49
79
|
puts "😎 Vibe mode ON" if @vibe
|
50
80
|
end
|
51
81
|
|
52
|
-
# Use existing session path
|
53
|
-
session_path = @restore_session_path
|
54
|
-
@session_path = session_path
|
55
|
-
@session_log_path = File.join(@session_path, "session.log")
|
56
|
-
ENV["CLAUDE_SWARM_SESSION_PATH"] = session_path
|
57
|
-
ENV["CLAUDE_SWARM_ROOT_DIR"] = ClaudeSwarm.root_dir
|
58
|
-
|
59
82
|
# Create run symlink for restored session
|
60
83
|
create_run_symlink
|
61
84
|
|
62
85
|
non_interactive_output do
|
63
|
-
puts "📝 Using existing session: #{session_path}/"
|
86
|
+
puts "📝 Using existing session: #{@session_path}/"
|
64
87
|
end
|
65
88
|
|
66
|
-
# Initialize process tracker
|
67
|
-
@process_tracker = ProcessTracker.new(session_path)
|
68
|
-
|
69
89
|
# Check if the original session used worktrees
|
70
|
-
restore_worktrees_if_needed(session_path)
|
90
|
+
restore_worktrees_if_needed(@session_path)
|
71
91
|
|
72
92
|
# Regenerate MCP configurations with session IDs for restoration
|
73
93
|
@generator.generate_all
|
74
94
|
non_interactive_output do
|
75
95
|
puts "✓ Regenerated MCP configurations with session IDs"
|
76
96
|
end
|
97
|
+
|
98
|
+
# Generate settings files
|
99
|
+
@settings_generator.generate_all
|
100
|
+
non_interactive_output do
|
101
|
+
puts "✓ Generated settings files with hooks"
|
102
|
+
end
|
77
103
|
else
|
78
104
|
non_interactive_output do
|
79
105
|
puts "🐝 Starting Claude Swarm: #{@config.swarm_name}"
|
80
106
|
puts "😎 Vibe mode ON" if @vibe
|
81
107
|
end
|
82
108
|
|
83
|
-
# Generate and set session path for all instances
|
84
|
-
session_params = { working_dir: ClaudeSwarm.root_dir }
|
85
|
-
session_params[:session_id] = @provided_session_id if @provided_session_id
|
86
|
-
session_path = SessionPath.generate(**session_params)
|
87
|
-
SessionPath.ensure_directory(session_path)
|
88
|
-
@session_path = session_path
|
89
|
-
@session_log_path = File.join(@session_path, "session.log")
|
90
|
-
|
91
|
-
# Extract session ID from path (the timestamp part)
|
92
|
-
@session_id = File.basename(session_path)
|
93
|
-
|
94
|
-
ENV["CLAUDE_SWARM_SESSION_PATH"] = session_path
|
95
|
-
ENV["CLAUDE_SWARM_ROOT_DIR"] = ClaudeSwarm.root_dir
|
96
|
-
|
97
109
|
# Create run symlink for new session
|
98
110
|
create_run_symlink
|
99
111
|
|
100
112
|
non_interactive_output do
|
101
|
-
puts "📝 Session files will be saved to: #{session_path}/"
|
113
|
+
puts "📝 Session files will be saved to: #{@session_path}/"
|
102
114
|
end
|
103
115
|
|
104
|
-
#
|
105
|
-
@
|
106
|
-
|
107
|
-
# Create WorktreeManager if needed with session ID
|
108
|
-
if @needs_worktree_manager
|
109
|
-
cli_option = @worktree_option.is_a?(String) && !@worktree_option.empty? ? @worktree_option : nil
|
110
|
-
@worktree_manager = WorktreeManager.new(cli_option, session_id: @session_id)
|
116
|
+
# Setup worktrees if needed
|
117
|
+
if @worktree_manager
|
111
118
|
non_interactive_output { print("🌳 Setting up Git worktrees...") }
|
112
119
|
|
113
120
|
# Get all instances for worktree setup
|
@@ -127,8 +134,14 @@ module ClaudeSwarm
|
|
127
134
|
puts "✓ Generated MCP configurations in session directory"
|
128
135
|
end
|
129
136
|
|
137
|
+
# Generate settings files
|
138
|
+
@settings_generator.generate_all
|
139
|
+
non_interactive_output do
|
140
|
+
puts "✓ Generated settings files with hooks"
|
141
|
+
end
|
142
|
+
|
130
143
|
# Save swarm config path for restoration
|
131
|
-
save_swarm_config_path(session_path)
|
144
|
+
save_swarm_config_path(@session_path)
|
132
145
|
end
|
133
146
|
|
134
147
|
# Launch the main instance (fetch after worktree setup to get modified paths)
|
@@ -159,6 +172,9 @@ module ClaudeSwarm
|
|
159
172
|
log_thread = nil
|
160
173
|
log_thread = start_log_streaming if @non_interactive_prompt && @stream_logs
|
161
174
|
|
175
|
+
# Start transcript tailing thread for main instance
|
176
|
+
@transcript_thread = start_transcript_tailing
|
177
|
+
|
162
178
|
# Write the current process PID (orchestrator) to a file for easy access
|
163
179
|
main_pid_file = File.join(@session_path, "main_pid")
|
164
180
|
File.write(main_pid_file, Process.pid.to_s)
|
@@ -204,6 +220,9 @@ module ClaudeSwarm
|
|
204
220
|
log_thread.join
|
205
221
|
end
|
206
222
|
|
223
|
+
# Clean up transcript tailing thread
|
224
|
+
cleanup_transcript_thread
|
225
|
+
|
207
226
|
# Display runtime and cost summary
|
208
227
|
display_summary
|
209
228
|
|
@@ -276,6 +295,7 @@ module ClaudeSwarm
|
|
276
295
|
|
277
296
|
def cleanup_processes
|
278
297
|
@process_tracker.cleanup_all
|
298
|
+
cleanup_transcript_thread
|
279
299
|
puts "✓ Cleanup complete"
|
280
300
|
rescue StandardError => e
|
281
301
|
puts "⚠️ Error during cleanup: #{e.message}"
|
@@ -488,6 +508,13 @@ module ClaudeSwarm
|
|
488
508
|
parts << "--mcp-config"
|
489
509
|
parts << mcp_config_path
|
490
510
|
|
511
|
+
# Add settings file if it exists for the main instance
|
512
|
+
settings_file = @settings_generator.settings_path(@config.main_instance)
|
513
|
+
if File.exist?(settings_file)
|
514
|
+
parts << "--settings"
|
515
|
+
parts << settings_file
|
516
|
+
end
|
517
|
+
|
491
518
|
# Handle different modes
|
492
519
|
if @non_interactive_prompt
|
493
520
|
# Non-interactive mode with -p
|
@@ -556,6 +583,86 @@ module ClaudeSwarm
|
|
556
583
|
end
|
557
584
|
end
|
558
585
|
|
586
|
+
def start_transcript_tailing
|
587
|
+
Thread.new do
|
588
|
+
path_file = File.join(@session_path, "main_instance_transcript.path")
|
589
|
+
|
590
|
+
# Wait for path file to exist (created by SessionStart hook)
|
591
|
+
sleep(0.5) until File.exist?(path_file)
|
592
|
+
|
593
|
+
# Read the transcript path
|
594
|
+
transcript_path = File.read(path_file).strip
|
595
|
+
|
596
|
+
# Wait for transcript file to exist
|
597
|
+
sleep(0.5) until File.exist?(transcript_path)
|
598
|
+
|
599
|
+
# Tail the transcript file continuously (like tail -f)
|
600
|
+
File.open(transcript_path, "r") do |file|
|
601
|
+
# Start from the beginning to capture all entries
|
602
|
+
file.seek(0, IO::SEEK_SET) # Start at beginning of file
|
603
|
+
|
604
|
+
loop do
|
605
|
+
line = file.gets
|
606
|
+
if line
|
607
|
+
begin
|
608
|
+
# Parse JSONL entry
|
609
|
+
transcript_entry = JSON.parse(line)
|
610
|
+
|
611
|
+
# Skip summary entries - these are just conversation titles
|
612
|
+
next if transcript_entry["type"] == "summary"
|
613
|
+
|
614
|
+
# Convert to session.log.json format
|
615
|
+
session_entry = convert_transcript_to_session_format(transcript_entry)
|
616
|
+
|
617
|
+
# Write with file locking (same pattern as BaseExecutor)
|
618
|
+
session_json_path = File.join(@session_path, "session.log.json")
|
619
|
+
File.open(session_json_path, File::WRONLY | File::APPEND | File::CREAT) do |log_file|
|
620
|
+
log_file.flock(File::LOCK_EX)
|
621
|
+
log_file.puts(session_entry.to_json)
|
622
|
+
log_file.flock(File::LOCK_UN)
|
623
|
+
end
|
624
|
+
rescue JSON::ParserError
|
625
|
+
# Silently skip unparseable lines
|
626
|
+
rescue StandardError
|
627
|
+
# Silently handle other errors to keep thread running
|
628
|
+
end
|
629
|
+
else
|
630
|
+
# No new data, sleep briefly
|
631
|
+
sleep(0.1)
|
632
|
+
end
|
633
|
+
end
|
634
|
+
end
|
635
|
+
rescue StandardError
|
636
|
+
# Silently handle thread errors
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
def convert_transcript_to_session_format(transcript_entry)
|
641
|
+
{
|
642
|
+
instance: @config.main_instance,
|
643
|
+
instance_id: "main",
|
644
|
+
timestamp: transcript_entry["timestamp"] || Time.now.iso8601,
|
645
|
+
event: {
|
646
|
+
type: "transcript",
|
647
|
+
source: "main_instance",
|
648
|
+
data: transcript_entry,
|
649
|
+
},
|
650
|
+
}
|
651
|
+
end
|
652
|
+
|
653
|
+
def cleanup_transcript_thread
|
654
|
+
return unless @transcript_thread
|
655
|
+
|
656
|
+
@transcript_thread.terminate if @transcript_thread.alive?
|
657
|
+
@transcript_thread.join(1) # Wait up to 1 second for thread to finish
|
658
|
+
rescue StandardError => e
|
659
|
+
logger.error { "Error cleaning up transcript thread: #{e.message}" }
|
660
|
+
end
|
661
|
+
|
662
|
+
def logger
|
663
|
+
@logger ||= Logger.new(File.join(@session_path, "session.log"), level: :info, progname: "orchestrator")
|
664
|
+
end
|
665
|
+
|
559
666
|
def execute_commands(commands, phase:, fail_fast:)
|
560
667
|
all_succeeded = true
|
561
668
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClaudeSwarm
|
4
|
+
class SettingsGenerator
|
5
|
+
def initialize(configuration)
|
6
|
+
@config = configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate_all
|
10
|
+
ensure_session_directory
|
11
|
+
|
12
|
+
@config.instances.each do |name, instance|
|
13
|
+
generate_settings(name, instance)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def settings_path(instance_name)
|
18
|
+
File.join(session_path, "#{instance_name}_settings.json")
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def session_path
|
24
|
+
@session_path ||= SessionPath.from_env
|
25
|
+
end
|
26
|
+
|
27
|
+
def ensure_session_directory
|
28
|
+
# Session directory is already created by orchestrator
|
29
|
+
# Just ensure it exists
|
30
|
+
SessionPath.ensure_directory(session_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_settings(name, instance)
|
34
|
+
settings = {}
|
35
|
+
|
36
|
+
# Add hooks if configured
|
37
|
+
if instance[:hooks] && !instance[:hooks].empty?
|
38
|
+
settings["hooks"] = instance[:hooks]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add SessionStart hook for main instance to capture transcript path
|
42
|
+
if name == @config.main_instance
|
43
|
+
session_start_hook = build_session_start_hook
|
44
|
+
|
45
|
+
# Initialize hooks if not present
|
46
|
+
settings["hooks"] ||= {}
|
47
|
+
settings["hooks"]["SessionStart"] ||= []
|
48
|
+
|
49
|
+
# Add our hook to the SessionStart hooks
|
50
|
+
settings["hooks"]["SessionStart"] << session_start_hook
|
51
|
+
end
|
52
|
+
|
53
|
+
# Only write settings file if there are settings to write
|
54
|
+
return if settings.empty?
|
55
|
+
|
56
|
+
# Write settings file
|
57
|
+
File.write(settings_path(name), JSON.pretty_generate(settings))
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_session_start_hook
|
61
|
+
hook_script_path = File.expand_path("hooks/session_start_hook.rb", __dir__)
|
62
|
+
# Pass session path as an argument since ENV may not be inherited
|
63
|
+
session_path_arg = session_path
|
64
|
+
|
65
|
+
{
|
66
|
+
"matcher" => "startup",
|
67
|
+
"hooks" => [
|
68
|
+
{
|
69
|
+
"type" => "command",
|
70
|
+
"command" => "ruby #{hook_script_path} '#{session_path_arg}'",
|
71
|
+
"timeout" => 5,
|
72
|
+
},
|
73
|
+
],
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
You are a Claude Swarm configuration generator assistant. Your role is to help the user create a well-structured
|
1
|
+
You are a Claude Swarm configuration generator assistant. Your role is to help the user create a well-structured swarm YAML file through an interactive conversation.
|
2
2
|
|
3
3
|
## Claude Swarm Overview
|
4
4
|
Claude Swarm is a Ruby gem that 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).
|
@@ -10,6 +10,7 @@ Key capabilities:
|
|
10
10
|
- Run instances in different directories or Git worktrees
|
11
11
|
- Support for custom system prompts per instance
|
12
12
|
- Choose appropriate models (opus for complex tasks, sonnet for simpler ones)
|
13
|
+
- Support for OpenAI instances
|
13
14
|
|
14
15
|
## Your Task
|
15
16
|
1. Start by asking about the user's project structure and development needs
|
@@ -43,7 +44,7 @@ swarm:
|
|
43
44
|
instance_name:
|
44
45
|
description: "Clear description of role and responsibilities"
|
45
46
|
directory: ./path/to/directory
|
46
|
-
model:
|
47
|
+
model: opus
|
47
48
|
allowed_tools: [Read, Edit, Write, Bash]
|
48
49
|
connections: [other_instance_names] # Optional
|
49
50
|
prompt: |
|
@@ -57,7 +58,7 @@ swarm:
|
|
57
58
|
- Write clear descriptions explaining each instance's responsibilities
|
58
59
|
- Choose opus model for complex architectural or algorithmic tasks and routine development.
|
59
60
|
- Choose sonnet model for simpler tasks
|
60
|
-
- Set up logical connections (e.g., lead → team members, architect → implementers), but
|
61
|
+
- Set up logical connections (e.g., lead → team members, architect → implementers), but do not create circular dependencies.
|
61
62
|
- Always add this to the end of every prompt: `For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.`
|
62
63
|
- Select tools based on each instance's actual needs:
|
63
64
|
- Read: For code review and analysis roles
|
@@ -72,7 +73,6 @@ swarm:
|
|
72
73
|
|
73
74
|
## Interactive Questions to Ask
|
74
75
|
- What type of project are you working on?
|
75
|
-
- What's your project's directory structure?
|
76
76
|
- What are the main technologies/frameworks you're using?
|
77
77
|
- What development tasks do you need help with?
|
78
78
|
- Do you need specialized roles (testing, DevOps, documentation)?
|
@@ -225,6 +225,3 @@ The more precisely you explain what you want, the better Claude's response will
|
|
225
225
|
* **Provide instructions as sequential steps:** Use numbered lists or bullet points to better ensure that Claude carries out the task the exact way you want it to.
|
226
226
|
|
227
227
|
</prompt_best_practices>
|
228
|
-
|
229
|
-
Start the conversation by greeting the user and asking: "What kind of project would you like to create a Claude Swarm for?"
|
230
|
-
Say: I am ready to start
|
data/lib/claude_swarm/version.rb
CHANGED
data/lib/claude_swarm.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: claude_swarm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paulo Arruda
|
@@ -51,6 +51,20 @@ dependencies:
|
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '0.1'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: faraday-net_http_persistent
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.0'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.0'
|
54
68
|
- !ruby/object:Gem::Dependency
|
55
69
|
name: faraday-retry
|
56
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -143,6 +157,7 @@ files:
|
|
143
157
|
- examples/monitoring-demo.yml
|
144
158
|
- examples/multi-directory.yml
|
145
159
|
- examples/session-restoration-demo.yml
|
160
|
+
- examples/simple-session-hook-swarm.yml
|
146
161
|
- examples/test-generation.yml
|
147
162
|
- examples/with-before-commands.yml
|
148
163
|
- exe/claude-swarm
|
@@ -154,6 +169,7 @@ files:
|
|
154
169
|
- lib/claude_swarm/commands/ps.rb
|
155
170
|
- lib/claude_swarm/commands/show.rb
|
156
171
|
- lib/claude_swarm/configuration.rb
|
172
|
+
- lib/claude_swarm/hooks/session_start_hook.rb
|
157
173
|
- lib/claude_swarm/mcp_generator.rb
|
158
174
|
- lib/claude_swarm/openai/chat_completion.rb
|
159
175
|
- lib/claude_swarm/openai/executor.rb
|
@@ -162,6 +178,7 @@ files:
|
|
162
178
|
- lib/claude_swarm/process_tracker.rb
|
163
179
|
- lib/claude_swarm/session_cost_calculator.rb
|
164
180
|
- lib/claude_swarm/session_path.rb
|
181
|
+
- lib/claude_swarm/settings_generator.rb
|
165
182
|
- lib/claude_swarm/system_utils.rb
|
166
183
|
- lib/claude_swarm/templates/generation_prompt.md.erb
|
167
184
|
- lib/claude_swarm/tools/reset_session_tool.rb
|