claude_swarm 0.1.11 → 0.1.13
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 +47 -0
- data/README.md +85 -15
- data/claude-swarm.yml +42 -0
- data/example/session-restoration-demo.yml +19 -0
- data/lib/claude_swarm/claude_code_executor.rb +101 -20
- data/lib/claude_swarm/claude_mcp_server.rb +16 -4
- data/lib/claude_swarm/cli.rb +169 -5
- data/lib/claude_swarm/configuration.rb +33 -7
- data/lib/claude_swarm/mcp_generator.rb +62 -22
- data/lib/claude_swarm/orchestrator.rb +122 -24
- data/lib/claude_swarm/permission_mcp_server.rb +14 -15
- data/lib/claude_swarm/process_tracker.rb +78 -0
- data/lib/claude_swarm/session_path.rb +50 -0
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm.rb +8 -0
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57e09297e72fe54ef127b5f7b5789c9e9d825279404ffa96c29f2ff0e7fbc622
|
4
|
+
data.tar.gz: 6e1d719d97d2f8fefa4ec8d0e9396e5fba867012b94d88d915247aea18690523
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ad9a411c8bc503829927ea534fed74e9f525dd62d68fc6eb9978667afdb40435b9bac0c13320c097f1f41556a8d50215c723c8066dec7366cf7d847d07a7e9e
|
7
|
+
data.tar.gz: 490824249ac673b7904f05a084d2ff0190a1034529a9302f423d00e965cd7c793b519b1d8e318a36a1e58af80706cb1fda80dd507af525b45f7df26299abd3f9
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,50 @@
|
|
1
|
+
## [Unreleased]
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- **Session restoration support (Experimental)**: Session management with the ability to resume previous Claude Swarm sessions. Note: This is an experimental feature with limitations - the main instance's conversation context is not fully restored
|
5
|
+
- New `--session-id` flag to resume a session by ID or path
|
6
|
+
- New `list-sessions` command to view available sessions with metadata
|
7
|
+
- Automatic capture and persistence of Claude session IDs for all instances
|
8
|
+
- Individual instance states stored in `state/` directory with instance ID as filename (e.g., `state/lead_abc123.json`)
|
9
|
+
- Swarm configuration copied to session directory as `config.yml` for restoration
|
10
|
+
- **Instance ID tracking**: Each instance now gets a unique ID in the format `instance_name_<hex>` for better identification in logs
|
11
|
+
- **Enhanced logging with instance IDs**: All log messages now include instance IDs when available (e.g., `lead (lead_1234abcd) -> backend (backend_5678efgh)`)
|
12
|
+
- **Calling instance ID propagation**: When one instance calls another, both the calling instance name and ID are passed for complete tracking
|
13
|
+
- Instance IDs are stored in MCP configuration files with `instance_id` and `instance_name` fields
|
14
|
+
- New CLI options: `--instance-id` and `--calling-instance-id` for the `mcp-serve` command
|
15
|
+
- ClaudeCodeExecutor now tracks and logs both instance and calling instance IDs
|
16
|
+
- **Process tracking and cleanup**: Added automatic tracking and cleanup of child MCP server processes
|
17
|
+
- New `ProcessTracker` class creates individual PID files in a `pids/` directory within the session path
|
18
|
+
- Signal handlers (INT, TERM, QUIT) ensure all child processes are terminated when the main instance exits
|
19
|
+
- Prevents orphaned MCP server processes from continuing to run after swarm termination
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
- Human-readable logs improved to show instance IDs in parentheses after instance names for easier tracking of multi-instance interactions
|
23
|
+
- `log_request` method enhanced to include instance IDs in structured JSON logs
|
24
|
+
- Configuration class now accepts optional `base_dir` parameter to support session restoration from different directories
|
25
|
+
|
26
|
+
### Fixed
|
27
|
+
- Fixed issue where child MCP server processes would continue running after the main instance exits
|
28
|
+
|
29
|
+
## [0.1.12]
|
30
|
+
### Added
|
31
|
+
- **Circular dependency detection**: Configuration validation now detects and reports circular dependencies between instances
|
32
|
+
- Clear error messages showing the dependency cycle (e.g., "Circular dependency detected: lead -> backend -> lead")
|
33
|
+
- Comprehensive test coverage for various circular dependency scenarios
|
34
|
+
- **Session management improvements**: Session files are now stored in `~/.claude-swarm/sessions/` organized by project path
|
35
|
+
- Added `SessionPath` module to centralize session path management
|
36
|
+
- Sessions are now organized by project directory for better multi-project support
|
37
|
+
- Added `CLAUDE_SWARM_HOME` environment variable support for custom storage location
|
38
|
+
- Log full JSON to `session.log.json` as JSONL
|
39
|
+
|
40
|
+
### Changed
|
41
|
+
- Session files moved from `./.claude-swarm/sessions/` to `~/.claude-swarm/sessions/[project]/[timestamp]/`
|
42
|
+
- Replaced `CLAUDE_SWARM_SESSION_TIMESTAMP` with `CLAUDE_SWARM_SESSION_PATH` environment variable
|
43
|
+
- MCP server configurations now use the new centralized session path
|
44
|
+
|
45
|
+
### Fixed
|
46
|
+
- Fixed circular dependency example in README documentation
|
47
|
+
|
1
48
|
## [0.1.11]
|
2
49
|
### Added
|
3
50
|
- Main instance debug mode with `claude-swarm --debug`
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Claude Swarm
|
2
2
|
|
3
|
-
[](https://badge.fury.io/rb/claude_swarm)
|
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.
|
@@ -80,7 +80,7 @@ claude-swarm --vibe # That will allow ALL tools for all instances! Be Careful!
|
|
80
80
|
This will:
|
81
81
|
- Launch the main instance (lead) with connections to other instances
|
82
82
|
- The lead instance can communicate with the other instances via MCP
|
83
|
-
- All session files are stored in
|
83
|
+
- All session files are stored in `~/.claude-swarm/sessions/{project}/{timestamp}/` (customizable via `CLAUDE_SWARM_HOME`)
|
84
84
|
|
85
85
|
#### Multi-Level Swarm Example
|
86
86
|
|
@@ -327,7 +327,7 @@ swarm:
|
|
327
327
|
description: "Backend developer building APIs and services"
|
328
328
|
directory: ./backend
|
329
329
|
model: opus
|
330
|
-
connections: [
|
330
|
+
connections: [database]
|
331
331
|
allowed_tools:
|
332
332
|
- Edit
|
333
333
|
- Write
|
@@ -446,6 +446,14 @@ claude-swarm --vibe
|
|
446
446
|
claude-swarm -p "Implement the new user authentication feature"
|
447
447
|
claude-swarm --prompt "Fix the bug in the payment module"
|
448
448
|
|
449
|
+
# Resume a previous session by ID
|
450
|
+
claude-swarm --session-id 20241206_143022
|
451
|
+
claude-swarm --session-id ~/path/to/session
|
452
|
+
|
453
|
+
# List available sessions
|
454
|
+
claude-swarm list-sessions
|
455
|
+
claude-swarm list-sessions --limit 20
|
456
|
+
|
449
457
|
# Show version
|
450
458
|
claude-swarm version
|
451
459
|
|
@@ -457,6 +465,70 @@ claude-swarm tools-mcp --allowed-tools 'Read,Edit' --disallowed-tools 'Edit(*.lo
|
|
457
465
|
claude-swarm mcp-serve INSTANCE_NAME --config CONFIG_FILE --session-timestamp TIMESTAMP
|
458
466
|
```
|
459
467
|
|
468
|
+
### Session Management and Restoration (Experimental)
|
469
|
+
|
470
|
+
Claude Swarm provides experimental session management with restoration capabilities. **Note: This feature is experimental and has limitations - the main instance's conversation context is not fully restored.**
|
471
|
+
|
472
|
+
#### Session Structure
|
473
|
+
All session files are organized in `~/.claude-swarm/sessions/{project}/{timestamp}/`:
|
474
|
+
- `config.yml`: Copy of the original swarm configuration
|
475
|
+
- `state/`: Directory containing individual instance states
|
476
|
+
- `{instance_id}.json`: Claude session ID and status for each instance (e.g., `lead_abc123.json`)
|
477
|
+
- `{instance_name}.mcp.json`: MCP configuration files
|
478
|
+
- `session.log`: Human-readable request/response tracking
|
479
|
+
- `session.log.json`: All events in JSONL format (one JSON per line)
|
480
|
+
- `permissions.log`: Permission checks and decisions
|
481
|
+
|
482
|
+
#### Listing Sessions
|
483
|
+
View your previous Claude Swarm sessions:
|
484
|
+
|
485
|
+
```bash
|
486
|
+
# List recent sessions (default: 10)
|
487
|
+
claude-swarm list-sessions
|
488
|
+
|
489
|
+
# List more sessions
|
490
|
+
claude-swarm list-sessions --limit 20
|
491
|
+
```
|
492
|
+
|
493
|
+
Output shows:
|
494
|
+
- Session ID (timestamp)
|
495
|
+
- Creation time
|
496
|
+
- Main instance name
|
497
|
+
- Number of instances
|
498
|
+
- Configuration file used
|
499
|
+
- Full session path
|
500
|
+
|
501
|
+
#### Resuming Sessions
|
502
|
+
Resume a previous session with all instances restored to their Claude session states:
|
503
|
+
|
504
|
+
```bash
|
505
|
+
# Resume by session ID
|
506
|
+
claude-swarm --session-id 20241206_143022
|
507
|
+
|
508
|
+
# Resume by full path
|
509
|
+
claude-swarm --session-id ~/.claude-swarm/sessions/my-project/20241206_143022
|
510
|
+
```
|
511
|
+
|
512
|
+
This will:
|
513
|
+
1. Load the session manifest and instance states
|
514
|
+
2. Restore the original swarm configuration
|
515
|
+
3. Resume the main instance with its Claude session ID
|
516
|
+
4. Restore all connected instances with their session IDs
|
517
|
+
5. Maintain the same working directories and tool permissions
|
518
|
+
|
519
|
+
#### How Session Restoration Works
|
520
|
+
- Each instance's Claude session ID is automatically captured and persisted
|
521
|
+
- Instance states are stored in separate files named by instance ID to prevent concurrency issues
|
522
|
+
- MCP configurations are regenerated with the saved session IDs
|
523
|
+
- The main instance uses Claude's `--resume` flag (limited effectiveness)
|
524
|
+
- Connected instances receive their session IDs via `--claude-session-id`
|
525
|
+
|
526
|
+
**Important Limitations:**
|
527
|
+
- The main instance's conversation history and context are not fully restored
|
528
|
+
- Only the session ID is preserved, not the actual conversation state
|
529
|
+
- Connected instances restore more reliably than the main instance
|
530
|
+
- This is an experimental feature and may not work as expected
|
531
|
+
|
460
532
|
## How It Works
|
461
533
|
|
462
534
|
1. **Configuration Parsing**: Claude Swarm reads your YAML configuration and validates it
|
@@ -471,20 +543,17 @@ claude-swarm mcp-serve INSTANCE_NAME --config CONFIG_FILE --session-timestamp TI
|
|
471
543
|
- Eliminates the need to manually accept each tool or use global `--vibe` mode
|
472
544
|
- Per-instance `vibe: true` skips all permission checks for that specific instance
|
473
545
|
- The permission MCP uses `--permission-prompt-tool` to check tool access
|
474
|
-
- Permission decisions are logged to
|
475
|
-
4. **Session
|
476
|
-
- Generates a shared session
|
477
|
-
- Each instance
|
478
|
-
-
|
546
|
+
- Permission decisions are logged to `~/.claude-swarm/sessions/{project}/{timestamp}/permissions.log`
|
547
|
+
4. **Session Persistence**: Claude Swarm automatically tracks session state:
|
548
|
+
- Generates a shared session path for all instances
|
549
|
+
- Each instance's Claude session ID is captured and saved
|
550
|
+
- Instance states are stored using instance IDs as filenames to avoid conflicts
|
551
|
+
- Sessions can be fully restored with all instances reconnected
|
479
552
|
5. **Main Instance Launch**: The main instance is launched with its MCP configuration, giving it access to all connected instances
|
480
553
|
6. **Inter-Instance Communication**: Connected instances expose themselves as MCP servers with these tools:
|
481
554
|
- **task**: Execute tasks using Claude Code with configurable tools and return results. The tool description includes the instance name and description (e.g., "Execute a task using Agent frontend_dev. Frontend developer specializing in React and TypeScript")
|
482
555
|
- **session_info**: Get current Claude session information including ID and working directory
|
483
556
|
- **reset_session**: Reset the Claude session for a fresh start
|
484
|
-
7. **Session Management**: All session files are organized in `.claude-swarm/sessions/{timestamp}/`:
|
485
|
-
- MCP configuration files: `{instance_name}.mcp.json`
|
486
|
-
- Session log: `session.log` with detailed request/response tracking
|
487
|
-
- Permission log: `permissions.log` with all permission checks and decisions
|
488
557
|
|
489
558
|
## Troubleshooting
|
490
559
|
|
@@ -507,14 +576,15 @@ claude-swarm mcp-serve INSTANCE_NAME --config CONFIG_FILE --session-timestamp TI
|
|
507
576
|
### Debug Output
|
508
577
|
|
509
578
|
The swarm will display:
|
510
|
-
- Session directory location (
|
579
|
+
- Session directory location (`~/.claude-swarm/sessions/{project}/{timestamp}/`)
|
511
580
|
- Main instance details (model, directory, tools, connections)
|
512
581
|
- The exact command being run
|
513
582
|
|
514
583
|
### Session Files
|
515
584
|
|
516
|
-
Check the session directory
|
517
|
-
- `session.log`:
|
585
|
+
Check the session directory `~/.claude-swarm/sessions/{project}/{timestamp}/` for:
|
586
|
+
- `session.log`: Human-readable logs with request/response tracking
|
587
|
+
- `session.log.json`: All events in JSONL format (one JSON object per line)
|
518
588
|
- `{instance}.mcp.json`: MCP configuration for each instance
|
519
589
|
- All files for a session are kept together for easy review
|
520
590
|
|
data/claude-swarm.yml
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
version: 1
|
2
|
+
swarm:
|
3
|
+
name: "Swarm Name"
|
4
|
+
main: lead_developer
|
5
|
+
instances:
|
6
|
+
lead_developer:
|
7
|
+
description: "Lead developer who coordinates the team and makes architectural decisions"
|
8
|
+
directory: .
|
9
|
+
model: sonnet
|
10
|
+
prompt: "You are the lead developer coordinating the team"
|
11
|
+
allowed_tools: [Read, Edit, Bash, Write]
|
12
|
+
connections: [frontend_dev, backend_dev]
|
13
|
+
|
14
|
+
# Example instances (uncomment and modify as needed):
|
15
|
+
|
16
|
+
frontend_dev:
|
17
|
+
description: "Frontend developer specializing in React and modern web technologies"
|
18
|
+
directory: .
|
19
|
+
model: sonnet
|
20
|
+
prompt: "You specialize in frontend development with React, TypeScript, and modern web technologies"
|
21
|
+
allowed_tools: [Read, Edit, Write, "Bash(npm:*)", "Bash(yarn:*)", "Bash(pnpm:*)"]
|
22
|
+
|
23
|
+
backend_dev:
|
24
|
+
description: "Backend developer focusing on APIs, databases, and server architecture"
|
25
|
+
directory: .
|
26
|
+
model: sonnet
|
27
|
+
prompt: "You specialize in backend development, APIs, databases, and server architecture"
|
28
|
+
allowed_tools: [Read, Edit, Write, Bash]
|
29
|
+
|
30
|
+
# devops_engineer:
|
31
|
+
# description: "DevOps engineer managing infrastructure, CI/CD, and deployments"
|
32
|
+
# directory: .
|
33
|
+
# model: sonnet
|
34
|
+
# prompt: "You specialize in infrastructure, CI/CD, containerization, and deployment"
|
35
|
+
# allowed_tools: [Read, Edit, Write, "Bash(docker:*)", "Bash(kubectl:*)", "Bash(terraform:*)"]
|
36
|
+
|
37
|
+
# qa_engineer:
|
38
|
+
# description: "QA engineer ensuring quality through comprehensive testing"
|
39
|
+
# directory: ./tests
|
40
|
+
# model: sonnet
|
41
|
+
# prompt: "You specialize in testing, quality assurance, and test automation"
|
42
|
+
# allowed_tools: [Read, Edit, Write, Bash]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
version: 1
|
2
|
+
swarm:
|
3
|
+
name: "Session Restoration Demo"
|
4
|
+
main: coordinator
|
5
|
+
instances:
|
6
|
+
coordinator:
|
7
|
+
description: "Main coordinator that manages the team"
|
8
|
+
directory: .
|
9
|
+
model: sonnet
|
10
|
+
prompt: "You are the team coordinator. You help manage tasks and coordinate between team members."
|
11
|
+
allowed_tools: [Read, Edit, Bash]
|
12
|
+
connections: [developer]
|
13
|
+
|
14
|
+
developer:
|
15
|
+
description: "Developer who writes code"
|
16
|
+
directory: .
|
17
|
+
model: sonnet
|
18
|
+
prompt: "You are a skilled developer who writes clean, well-tested code."
|
19
|
+
allowed_tools: [Read, Edit, Write, Bash]
|
@@ -4,23 +4,25 @@ require "json"
|
|
4
4
|
require "open3"
|
5
5
|
require "logger"
|
6
6
|
require "fileutils"
|
7
|
+
require_relative "session_path"
|
7
8
|
|
8
9
|
module ClaudeSwarm
|
9
10
|
class ClaudeCodeExecutor
|
10
|
-
|
11
|
-
SESSIONS_DIR = "sessions"
|
11
|
+
attr_reader :session_id, :last_response, :working_directory, :logger, :session_path
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def initialize(working_directory: Dir.pwd, model: nil, mcp_config: nil, vibe: false,
|
14
|
+
instance_name: nil, instance_id: nil, calling_instance: nil, calling_instance_id: nil,
|
15
|
+
claude_session_id: nil)
|
16
16
|
@working_directory = working_directory
|
17
17
|
@model = model
|
18
18
|
@mcp_config = mcp_config
|
19
19
|
@vibe = vibe
|
20
|
-
@session_id =
|
20
|
+
@session_id = claude_session_id
|
21
21
|
@last_response = nil
|
22
22
|
@instance_name = instance_name
|
23
|
+
@instance_id = instance_id
|
23
24
|
@calling_instance = calling_instance
|
25
|
+
@calling_instance_id = calling_instance_id
|
24
26
|
|
25
27
|
# Setup logging
|
26
28
|
setup_logging
|
@@ -53,7 +55,10 @@ module ClaudeSwarm
|
|
53
55
|
log_streaming_event(json_data)
|
54
56
|
|
55
57
|
# Capture session_id from system init
|
56
|
-
|
58
|
+
if json_data["type"] == "system" && json_data["subtype"] == "init"
|
59
|
+
@session_id = json_data["session_id"]
|
60
|
+
write_instance_state
|
61
|
+
end
|
57
62
|
|
58
63
|
# Capture the final result
|
59
64
|
result_response = json_data if json_data["type"] == "result"
|
@@ -94,18 +99,35 @@ module ClaudeSwarm
|
|
94
99
|
|
95
100
|
private
|
96
101
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
|
102
|
+
def write_instance_state
|
103
|
+
return unless @instance_id && @session_id
|
104
|
+
|
105
|
+
state_dir = File.join(@session_path, "state")
|
106
|
+
FileUtils.mkdir_p(state_dir)
|
107
|
+
|
108
|
+
state_file = File.join(state_dir, "#{@instance_id}.json")
|
109
|
+
state_data = {
|
110
|
+
instance_name: @instance_name,
|
111
|
+
instance_id: @instance_id,
|
112
|
+
claude_session_id: @session_id,
|
113
|
+
status: "active",
|
114
|
+
updated_at: Time.now.iso8601
|
115
|
+
}
|
101
116
|
|
102
|
-
|
103
|
-
|
104
|
-
|
117
|
+
File.write(state_file, JSON.pretty_generate(state_data))
|
118
|
+
@logger.info("Wrote instance state for #{@instance_name} (#{@instance_id}) with session ID: #{@session_id}")
|
119
|
+
rescue StandardError => e
|
120
|
+
@logger.error("Failed to write instance state for #{@instance_name} (#{@instance_id}): #{e.message}")
|
121
|
+
end
|
122
|
+
|
123
|
+
def setup_logging
|
124
|
+
# Use session path from environment (required)
|
125
|
+
@session_path = SessionPath.from_env
|
126
|
+
SessionPath.ensure_directory(@session_path)
|
105
127
|
|
106
128
|
# Create logger with session.log filename
|
107
129
|
log_filename = "session.log"
|
108
|
-
log_path = File.join(
|
130
|
+
log_path = File.join(@session_path, log_filename)
|
109
131
|
@logger = Logger.new(log_path)
|
110
132
|
@logger.level = Logger::INFO
|
111
133
|
|
@@ -114,20 +136,47 @@ module ClaudeSwarm
|
|
114
136
|
"[#{datetime.strftime("%Y-%m-%d %H:%M:%S.%L")}] [#{severity}] #{msg}\n"
|
115
137
|
end
|
116
138
|
|
117
|
-
|
139
|
+
return unless @instance_name
|
140
|
+
|
141
|
+
instance_info = @instance_name
|
142
|
+
instance_info += " (#{@instance_id})" if @instance_id
|
143
|
+
@logger.info("Started Claude Code executor for instance: #{instance_info}")
|
118
144
|
end
|
119
145
|
|
120
146
|
def log_request(prompt)
|
121
|
-
|
147
|
+
caller_info = @calling_instance
|
148
|
+
caller_info += " (#{@calling_instance_id})" if @calling_instance_id
|
149
|
+
instance_info = @instance_name
|
150
|
+
instance_info += " (#{@instance_id})" if @instance_id
|
151
|
+
@logger.info("#{caller_info} -> #{instance_info}: \n---\n#{prompt}\n---")
|
152
|
+
|
153
|
+
# Build event hash for JSON logging
|
154
|
+
event = {
|
155
|
+
type: "request",
|
156
|
+
from_instance: @calling_instance,
|
157
|
+
from_instance_id: @calling_instance_id,
|
158
|
+
to_instance: @instance_name,
|
159
|
+
to_instance_id: @instance_id,
|
160
|
+
prompt: prompt,
|
161
|
+
timestamp: Time.now.iso8601
|
162
|
+
}
|
163
|
+
|
164
|
+
append_to_session_json(event)
|
122
165
|
end
|
123
166
|
|
124
167
|
def log_response(response)
|
168
|
+
caller_info = @calling_instance
|
169
|
+
caller_info += " (#{@calling_instance_id})" if @calling_instance_id
|
170
|
+
instance_info = @instance_name
|
171
|
+
instance_info += " (#{@instance_id})" if @instance_id
|
125
172
|
@logger.info(
|
126
|
-
"($#{response["total_cost"]} - #{response["duration_ms"]}ms) #{
|
173
|
+
"($#{response["total_cost"]} - #{response["duration_ms"]}ms) #{instance_info} -> #{caller_info}: \n---\n#{response["result"]}\n---"
|
127
174
|
)
|
128
175
|
end
|
129
176
|
|
130
177
|
def log_streaming_event(event)
|
178
|
+
append_to_session_json(event)
|
179
|
+
|
131
180
|
return log_system_message(event) if event["type"] == "system"
|
132
181
|
|
133
182
|
# Add specific details based on event type
|
@@ -155,14 +204,18 @@ module ClaudeSwarm
|
|
155
204
|
arguments = tool_call["input"].to_json
|
156
205
|
arguments = "#{arguments[0..300]} ...}" if arguments.length > 300
|
157
206
|
|
207
|
+
instance_info = @instance_name
|
208
|
+
instance_info += " (#{@instance_id})" if @instance_id
|
158
209
|
@logger.info(
|
159
|
-
"Tool call from #{
|
210
|
+
"Tool call from #{instance_info} -> Tool: #{tool_call["name"]}, ID: #{tool_call["id"]}, Arguments: #{arguments}"
|
160
211
|
)
|
161
212
|
end
|
162
213
|
|
163
214
|
text = content.select { |c| c["type"] == "text" }
|
164
215
|
text.each do |t|
|
165
|
-
@
|
216
|
+
instance_info = @instance_name
|
217
|
+
instance_info += " (#{@instance_id})" if @instance_id
|
218
|
+
@logger.info("#{instance_info} is thinking:\n---\n#{t["text"]}\n---")
|
166
219
|
end
|
167
220
|
end
|
168
221
|
|
@@ -170,6 +223,34 @@ module ClaudeSwarm
|
|
170
223
|
@logger.debug("USER: #{JSON.pretty_generate(content)}")
|
171
224
|
end
|
172
225
|
|
226
|
+
def append_to_session_json(event)
|
227
|
+
json_filename = "session.log.json"
|
228
|
+
json_path = File.join(@session_path, json_filename)
|
229
|
+
|
230
|
+
# Use file locking to ensure thread-safe writes
|
231
|
+
File.open(json_path, File::WRONLY | File::APPEND | File::CREAT) do |file|
|
232
|
+
file.flock(File::LOCK_EX)
|
233
|
+
|
234
|
+
# Create entry with metadata
|
235
|
+
entry = {
|
236
|
+
instance: @instance_name,
|
237
|
+
instance_id: @instance_id,
|
238
|
+
calling_instance: @calling_instance,
|
239
|
+
calling_instance_id: @calling_instance_id,
|
240
|
+
timestamp: Time.now.iso8601,
|
241
|
+
event: event
|
242
|
+
}
|
243
|
+
|
244
|
+
# Write as single line JSON (JSONL format)
|
245
|
+
file.puts(entry.to_json)
|
246
|
+
|
247
|
+
file.flock(File::LOCK_UN)
|
248
|
+
end
|
249
|
+
rescue StandardError => e
|
250
|
+
@logger.error("Failed to append to session JSON: #{e.message}")
|
251
|
+
raise
|
252
|
+
end
|
253
|
+
|
173
254
|
def build_command_array(prompt, options)
|
174
255
|
cmd_array = ["claude"]
|
175
256
|
|
@@ -6,35 +6,47 @@ require_relative "claude_code_executor"
|
|
6
6
|
require_relative "task_tool"
|
7
7
|
require_relative "session_info_tool"
|
8
8
|
require_relative "reset_session_tool"
|
9
|
+
require_relative "process_tracker"
|
9
10
|
|
10
11
|
module ClaudeSwarm
|
11
12
|
class ClaudeMcpServer
|
12
13
|
# Class variables to share state with tool classes
|
13
14
|
class << self
|
14
|
-
attr_accessor :executor, :instance_config, :logger, :
|
15
|
+
attr_accessor :executor, :instance_config, :logger, :session_path, :calling_instance, :calling_instance_id
|
15
16
|
end
|
16
17
|
|
17
|
-
def initialize(instance_config, calling_instance:)
|
18
|
+
def initialize(instance_config, calling_instance:, calling_instance_id: nil)
|
18
19
|
@instance_config = instance_config
|
19
20
|
@calling_instance = calling_instance
|
21
|
+
@calling_instance_id = calling_instance_id
|
20
22
|
@executor = ClaudeCodeExecutor.new(
|
21
23
|
working_directory: instance_config[:directory],
|
22
24
|
model: instance_config[:model],
|
23
25
|
mcp_config: instance_config[:mcp_config_path],
|
24
26
|
vibe: instance_config[:vibe],
|
25
27
|
instance_name: instance_config[:name],
|
26
|
-
|
28
|
+
instance_id: instance_config[:instance_id],
|
29
|
+
calling_instance: calling_instance,
|
30
|
+
calling_instance_id: calling_instance_id,
|
31
|
+
claude_session_id: instance_config[:claude_session_id]
|
27
32
|
)
|
28
33
|
|
29
34
|
# Set class variables so tools can access them
|
30
35
|
self.class.executor = @executor
|
31
36
|
self.class.instance_config = @instance_config
|
32
37
|
self.class.logger = @executor.logger
|
33
|
-
self.class.
|
38
|
+
self.class.session_path = @executor.session_path
|
34
39
|
self.class.calling_instance = @calling_instance
|
40
|
+
self.class.calling_instance_id = @calling_instance_id
|
35
41
|
end
|
36
42
|
|
37
43
|
def start
|
44
|
+
# Track this process
|
45
|
+
if @executor.session_path && File.exist?(@executor.session_path)
|
46
|
+
tracker = ProcessTracker.new(@executor.session_path)
|
47
|
+
tracker.track_pid(Process.pid, "mcp_#{@instance_config[:name]}")
|
48
|
+
end
|
49
|
+
|
38
50
|
server = FastMcp::Server.new(
|
39
51
|
name: @instance_config[:name],
|
40
52
|
version: "1.0.0"
|