claude_swarm 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +3 -0
- data/lib/claude_swarm/base_executor.rb +133 -0
- data/lib/claude_swarm/claude_code_executor.rb +19 -137
- data/lib/claude_swarm/claude_mcp_server.rb +2 -1
- data/lib/claude_swarm/cli.rb +1 -0
- data/lib/claude_swarm/mcp_generator.rb +3 -1
- data/lib/claude_swarm/openai/chat_completion.rb +15 -15
- data/lib/claude_swarm/openai/executor.rb +69 -161
- data/lib/claude_swarm/openai/responses.rb +27 -27
- data/lib/claude_swarm/orchestrator.rb +153 -171
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm.rb +1 -0
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc0d22524516d5e0f2a76715c2a80b36d1a974a3ff7ba451c103c86cb4a36fd1
|
4
|
+
data.tar.gz: 4cebdba8032fc3dfe223033ba9b2890b5849dd5516a539ffdf57231bcec66c38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5ec9ed928d5e73d12eb1257571e3c2b45fc9db64dbd64e8f3186ea2df5f187927d4cc0a6e8eb9901cb8dbaf4679f442f340c85f6f6f728ca01f1d8b82db8bf8
|
7
|
+
data.tar.gz: 8fce151a6a47d68736fc62b6453515f46e12d293d2eee2f3e951f99510351941faf9f0b9c5f0497de37847fac282cc997ccebdbb11f477fc71b21b8514dd753d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
## [0.3.7]
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- **Main instance logging**: Captures main Claude instance output in `session.log` with prettified JSON format
|
5
|
+
|
6
|
+
### Changed
|
7
|
+
- **Updated claude-code-sdk-ruby dependency**: Bumped from 0.1.4 to 0.1.6
|
8
|
+
- Includes latest SDK improvements and bug fixes
|
9
|
+
|
10
|
+
## [0.3.6]
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- **SSE MCP server headers support**: SSE-type MCP servers can now include custom headers for authentication
|
14
|
+
- Supports headers like `Authorization: "Bearer ${TOKEN}"` in MCP configurations
|
15
|
+
- Environment variables in header values are automatically interpolated
|
16
|
+
- **Comprehensive retry middleware for OpenAI API calls**: Added robust retry logic for OpenAI provider to handle API errors gracefully
|
17
|
+
- Automatically retries on rate limit errors (429) with exponential backoff
|
18
|
+
- Retries on server errors (500, 502, 503, 504) and timeout errors
|
19
|
+
- Configurable retry attempts, delay, and backoff settings
|
20
|
+
- Detailed logging of retry attempts and errors
|
21
|
+
|
1
22
|
## [0.3.5]
|
2
23
|
|
3
24
|
### Changed
|
data/README.md
CHANGED
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClaudeSwarm
|
4
|
+
class BaseExecutor
|
5
|
+
attr_reader :session_id, :last_response, :working_directory, :logger, :session_path, :session_json_path, :instance_info
|
6
|
+
|
7
|
+
def initialize(working_directory: Dir.pwd, model: nil, mcp_config: nil, vibe: false,
|
8
|
+
instance_name: nil, instance_id: nil, calling_instance: nil, calling_instance_id: nil,
|
9
|
+
claude_session_id: nil, additional_directories: [], debug: false)
|
10
|
+
@working_directory = working_directory
|
11
|
+
@additional_directories = additional_directories
|
12
|
+
@model = model
|
13
|
+
@mcp_config = mcp_config
|
14
|
+
@vibe = vibe
|
15
|
+
@session_id = claude_session_id
|
16
|
+
@last_response = nil
|
17
|
+
@instance_name = instance_name
|
18
|
+
@instance_id = instance_id
|
19
|
+
@calling_instance = calling_instance
|
20
|
+
@calling_instance_id = calling_instance_id
|
21
|
+
@debug = debug
|
22
|
+
|
23
|
+
# Setup static info strings for logging
|
24
|
+
@instance_info = build_info(@instance_name, @instance_id)
|
25
|
+
@caller_info = build_info(@calling_instance, @calling_instance_id)
|
26
|
+
@caller_to_instance = "#{@caller_info} -> #{instance_info}:"
|
27
|
+
@instance_to_caller = "#{instance_info} -> #{@caller_info}:"
|
28
|
+
|
29
|
+
# Setup logging
|
30
|
+
setup_logging
|
31
|
+
|
32
|
+
# Setup static event templates
|
33
|
+
setup_event_templates
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute(_prompt, _options = {})
|
37
|
+
raise NotImplementedError, "Subclasses must implement the execute method"
|
38
|
+
end
|
39
|
+
|
40
|
+
def reset_session
|
41
|
+
@session_id = nil
|
42
|
+
@last_response = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_session?
|
46
|
+
!@session_id.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def build_info(name, id)
|
52
|
+
return name unless id
|
53
|
+
|
54
|
+
"#{name} (#{id})"
|
55
|
+
end
|
56
|
+
|
57
|
+
def setup_logging
|
58
|
+
# Use session path from environment (required)
|
59
|
+
@session_path = SessionPath.from_env
|
60
|
+
SessionPath.ensure_directory(@session_path)
|
61
|
+
|
62
|
+
# Initialize session JSON path
|
63
|
+
@session_json_path = File.join(@session_path, "session.log.json")
|
64
|
+
|
65
|
+
# Create logger with session.log filename
|
66
|
+
log_filename = "session.log"
|
67
|
+
log_path = File.join(@session_path, log_filename)
|
68
|
+
log_level = @debug ? :debug : :info
|
69
|
+
@logger = Logger.new(log_path, level: log_level, progname: @instance_name)
|
70
|
+
|
71
|
+
logger.info { "Started #{self.class.name} for instance: #{instance_info}" }
|
72
|
+
end
|
73
|
+
|
74
|
+
def setup_event_templates
|
75
|
+
@log_request_event_template = {
|
76
|
+
type: "request",
|
77
|
+
from_instance: @calling_instance,
|
78
|
+
from_instance_id: @calling_instance_id,
|
79
|
+
to_instance: @instance_name,
|
80
|
+
to_instance_id: @instance_id,
|
81
|
+
}.freeze
|
82
|
+
|
83
|
+
@session_json_entry_template = {
|
84
|
+
instance: @instance_name,
|
85
|
+
instance_id: @instance_id,
|
86
|
+
calling_instance: @calling_instance,
|
87
|
+
calling_instance_id: @calling_instance_id,
|
88
|
+
}.freeze
|
89
|
+
end
|
90
|
+
|
91
|
+
def log_request(prompt)
|
92
|
+
logger.info { "#{@caller_to_instance} \n---\n#{prompt}\n---" }
|
93
|
+
|
94
|
+
# Merge dynamic data with static template
|
95
|
+
event = @log_request_event_template.merge(
|
96
|
+
prompt: prompt,
|
97
|
+
timestamp: Time.now.iso8601,
|
98
|
+
)
|
99
|
+
|
100
|
+
append_to_session_json(event)
|
101
|
+
end
|
102
|
+
|
103
|
+
def log_response(response)
|
104
|
+
logger.info do
|
105
|
+
"($#{response["total_cost"]} - #{response["duration_ms"]}ms) #{@instance_to_caller} \n---\n#{response["result"]}\n---"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def append_to_session_json(event)
|
110
|
+
# Use file locking to ensure thread-safe writes
|
111
|
+
File.open(@session_json_path, File::WRONLY | File::APPEND | File::CREAT) do |file|
|
112
|
+
file.flock(File::LOCK_EX)
|
113
|
+
|
114
|
+
# Merge dynamic data with static template
|
115
|
+
entry = @session_json_entry_template.merge(
|
116
|
+
timestamp: Time.now.iso8601,
|
117
|
+
event: event,
|
118
|
+
)
|
119
|
+
|
120
|
+
# Write as single line JSON (JSONL format)
|
121
|
+
file.puts(entry.to_json)
|
122
|
+
|
123
|
+
file.flock(File::LOCK_UN)
|
124
|
+
end
|
125
|
+
rescue StandardError => e
|
126
|
+
logger.error { "Failed to append to session JSON: #{e.message}" }
|
127
|
+
raise
|
128
|
+
end
|
129
|
+
|
130
|
+
class ExecutionError < StandardError; end
|
131
|
+
class ParseError < StandardError; end
|
132
|
+
end
|
133
|
+
end
|
@@ -1,28 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ClaudeSwarm
|
4
|
-
class ClaudeCodeExecutor
|
5
|
-
attr_reader :session_id, :last_response, :working_directory, :logger, :session_path
|
6
|
-
|
7
|
-
def initialize(working_directory: Dir.pwd, model: nil, mcp_config: nil, vibe: false,
|
8
|
-
instance_name: nil, instance_id: nil, calling_instance: nil, calling_instance_id: nil,
|
9
|
-
claude_session_id: nil, additional_directories: [])
|
10
|
-
@working_directory = working_directory
|
11
|
-
@additional_directories = additional_directories
|
12
|
-
@model = model
|
13
|
-
@mcp_config = mcp_config
|
14
|
-
@vibe = vibe
|
15
|
-
@session_id = claude_session_id
|
16
|
-
@last_response = nil
|
17
|
-
@instance_name = instance_name
|
18
|
-
@instance_id = instance_id
|
19
|
-
@calling_instance = calling_instance
|
20
|
-
@calling_instance_id = calling_instance_id
|
21
|
-
|
22
|
-
# Setup logging
|
23
|
-
setup_logging
|
24
|
-
end
|
25
|
-
|
4
|
+
class ClaudeCodeExecutor < BaseExecutor
|
26
5
|
def execute(prompt, options = {})
|
27
6
|
# Log the request
|
28
7
|
log_request(prompt)
|
@@ -75,8 +54,8 @@ module ClaudeSwarm
|
|
75
54
|
end
|
76
55
|
end
|
77
56
|
rescue StandardError => e
|
78
|
-
|
79
|
-
|
57
|
+
logger.error { "Execution error for #{@instance_name}: #{e.class} - #{e.message}" }
|
58
|
+
logger.error { "Backtrace: #{e.backtrace.join("\n")}" }
|
80
59
|
raise ExecutionError, "Claude Code execution failed: #{e.message}"
|
81
60
|
end
|
82
61
|
|
@@ -90,20 +69,11 @@ module ClaudeSwarm
|
|
90
69
|
|
91
70
|
result_response
|
92
71
|
rescue StandardError => e
|
93
|
-
|
94
|
-
|
72
|
+
logger.error { "Unexpected error for #{@instance_name}: #{e.class} - #{e.message}" }
|
73
|
+
logger.error { "Backtrace: #{e.backtrace.join("\n")}" }
|
95
74
|
raise
|
96
75
|
end
|
97
76
|
|
98
|
-
def reset_session
|
99
|
-
@session_id = nil
|
100
|
-
@last_response = nil
|
101
|
-
end
|
102
|
-
|
103
|
-
def has_session?
|
104
|
-
!@session_id.nil?
|
105
|
-
end
|
106
|
-
|
107
77
|
private
|
108
78
|
|
109
79
|
def write_instance_state
|
@@ -122,63 +92,9 @@ module ClaudeSwarm
|
|
122
92
|
}
|
123
93
|
|
124
94
|
File.write(state_file, JSON.pretty_generate(state_data))
|
125
|
-
|
95
|
+
logger.info { "Wrote instance state for #{@instance_name} (#{@instance_id}) with session ID: #{@session_id}" }
|
126
96
|
rescue StandardError => e
|
127
|
-
|
128
|
-
end
|
129
|
-
|
130
|
-
def setup_logging
|
131
|
-
# Use session path from environment (required)
|
132
|
-
@session_path = SessionPath.from_env
|
133
|
-
SessionPath.ensure_directory(@session_path)
|
134
|
-
|
135
|
-
# Create logger with session.log filename
|
136
|
-
log_filename = "session.log"
|
137
|
-
log_path = File.join(@session_path, log_filename)
|
138
|
-
@logger = Logger.new(log_path)
|
139
|
-
@logger.level = Logger::INFO
|
140
|
-
|
141
|
-
# Custom formatter for better readability
|
142
|
-
@logger.formatter = proc do |severity, datetime, _progname, msg|
|
143
|
-
"[#{datetime.strftime("%Y-%m-%d %H:%M:%S.%L")}] [#{severity}] #{msg}\n"
|
144
|
-
end
|
145
|
-
|
146
|
-
return unless @instance_name
|
147
|
-
|
148
|
-
instance_info = @instance_name
|
149
|
-
instance_info += " (#{@instance_id})" if @instance_id
|
150
|
-
@logger.info("Started Claude Code executor for instance: #{instance_info}")
|
151
|
-
end
|
152
|
-
|
153
|
-
def log_request(prompt)
|
154
|
-
caller_info = @calling_instance
|
155
|
-
caller_info += " (#{@calling_instance_id})" if @calling_instance_id
|
156
|
-
instance_info = @instance_name
|
157
|
-
instance_info += " (#{@instance_id})" if @instance_id
|
158
|
-
@logger.info("#{caller_info} -> #{instance_info}: \n---\n#{prompt}\n---")
|
159
|
-
|
160
|
-
# Build event hash for JSON logging
|
161
|
-
event = {
|
162
|
-
type: "request",
|
163
|
-
from_instance: @calling_instance,
|
164
|
-
from_instance_id: @calling_instance_id,
|
165
|
-
to_instance: @instance_name,
|
166
|
-
to_instance_id: @instance_id,
|
167
|
-
prompt: prompt,
|
168
|
-
timestamp: Time.now.iso8601,
|
169
|
-
}
|
170
|
-
|
171
|
-
append_to_session_json(event)
|
172
|
-
end
|
173
|
-
|
174
|
-
def log_response(response)
|
175
|
-
caller_info = @calling_instance
|
176
|
-
caller_info += " (#{@calling_instance_id})" if @calling_instance_id
|
177
|
-
instance_info = @instance_name
|
178
|
-
instance_info += " (#{@instance_id})" if @instance_id
|
179
|
-
@logger.info(
|
180
|
-
"($#{response["total_cost"]} - #{response["duration_ms"]}ms) #{instance_info} -> #{caller_info}: \n---\n#{response["result"]}\n---",
|
181
|
-
)
|
97
|
+
logger.error { "Failed to write instance state for #{@instance_name} (#{@instance_id}): #{e.message}" }
|
182
98
|
end
|
183
99
|
|
184
100
|
def log_streaming_event(event)
|
@@ -198,13 +114,13 @@ module ClaudeSwarm
|
|
198
114
|
end
|
199
115
|
|
200
116
|
def log_system_message(event)
|
201
|
-
|
117
|
+
logger.debug { "SYSTEM: #{JSON.pretty_generate(event)}" }
|
202
118
|
end
|
203
119
|
|
204
120
|
def log_assistant_message(msg)
|
205
121
|
# Assistant messages don't have stop_reason in SDK - they only have content
|
206
122
|
content = msg["content"]
|
207
|
-
|
123
|
+
logger.debug { "ASSISTANT: #{JSON.pretty_generate(content)}" } if content
|
208
124
|
|
209
125
|
# Log tool calls
|
210
126
|
tool_calls = content&.select { |c| c["type"] == "tool_use" } || []
|
@@ -212,52 +128,20 @@ module ClaudeSwarm
|
|
212
128
|
arguments = tool_call["input"].to_json
|
213
129
|
arguments = "#{arguments[0..300]} ...}" if arguments.length > 300
|
214
130
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
"Tool call from #{instance_info} -> Tool: #{tool_call["name"]}, ID: #{tool_call["id"]}, Arguments: #{arguments}",
|
219
|
-
)
|
131
|
+
logger.info do
|
132
|
+
"Tool call from #{instance_info} -> Tool: #{tool_call["name"]}, ID: #{tool_call["id"]}, Arguments: #{arguments}"
|
133
|
+
end
|
220
134
|
end
|
221
135
|
|
222
136
|
# Log thinking text
|
223
137
|
text = content&.select { |c| c["type"] == "text" } || []
|
224
138
|
text.each do |t|
|
225
|
-
instance_info
|
226
|
-
instance_info += " (#{@instance_id})" if @instance_id
|
227
|
-
@logger.info("#{instance_info} is thinking:\n---\n#{t["text"]}\n---")
|
139
|
+
logger.info { "#{instance_info} is thinking:\n---\n#{t["text"]}\n---" }
|
228
140
|
end
|
229
141
|
end
|
230
142
|
|
231
143
|
def log_user_message(content)
|
232
|
-
|
233
|
-
end
|
234
|
-
|
235
|
-
def append_to_session_json(event)
|
236
|
-
json_filename = "session.log.json"
|
237
|
-
json_path = File.join(@session_path, json_filename)
|
238
|
-
|
239
|
-
# Use file locking to ensure thread-safe writes
|
240
|
-
File.open(json_path, File::WRONLY | File::APPEND | File::CREAT) do |file|
|
241
|
-
file.flock(File::LOCK_EX)
|
242
|
-
|
243
|
-
# Create entry with metadata
|
244
|
-
entry = {
|
245
|
-
instance: @instance_name,
|
246
|
-
instance_id: @instance_id,
|
247
|
-
calling_instance: @calling_instance,
|
248
|
-
calling_instance_id: @calling_instance_id,
|
249
|
-
timestamp: Time.now.iso8601,
|
250
|
-
event: event,
|
251
|
-
}
|
252
|
-
|
253
|
-
# Write as single line JSON (JSONL format)
|
254
|
-
file.puts(entry.to_json)
|
255
|
-
|
256
|
-
file.flock(File::LOCK_UN)
|
257
|
-
end
|
258
|
-
rescue StandardError => e
|
259
|
-
@logger.error("Failed to append to session JSON: #{e.message}")
|
260
|
-
raise
|
144
|
+
logger.debug { "USER: #{JSON.pretty_generate(content)}" }
|
261
145
|
end
|
262
146
|
|
263
147
|
def build_sdk_options(prompt, options)
|
@@ -265,7 +149,8 @@ module ClaudeSwarm
|
|
265
149
|
sdk_options = ClaudeSDK::ClaudeCodeOptions.new
|
266
150
|
|
267
151
|
# Basic options
|
268
|
-
|
152
|
+
# Only set model if ANTHROPIC_MODEL env var is not set
|
153
|
+
sdk_options.model = @model if @model && !ENV["ANTHROPIC_MODEL"]
|
269
154
|
sdk_options.cwd = @working_directory
|
270
155
|
sdk_options.resume = @session_id if @session_id && !options[:new_session]
|
271
156
|
|
@@ -328,14 +213,14 @@ module ClaudeSwarm
|
|
328
213
|
headers: server_config["headers"] || {},
|
329
214
|
)
|
330
215
|
else
|
331
|
-
|
216
|
+
logger.warn { "Unsupported MCP server type: #{server_type} for server: #{name}" }
|
332
217
|
nil
|
333
218
|
end
|
334
219
|
end
|
335
220
|
|
336
221
|
mcp_servers.compact
|
337
222
|
rescue StandardError => e
|
338
|
-
|
223
|
+
logger.error { "Failed to parse MCP config: #{e.message}" }
|
339
224
|
{}
|
340
225
|
end
|
341
226
|
|
@@ -346,7 +231,7 @@ module ClaudeSwarm
|
|
346
231
|
@additional_directories.each do |dir|
|
347
232
|
# This is a placeholder - the SDK doesn't directly support file system servers
|
348
233
|
# You would need to implement a proper MCP server that provides file access
|
349
|
-
|
234
|
+
logger.warn { "Additional directories not fully supported: #{dir}" }
|
350
235
|
end
|
351
236
|
end
|
352
237
|
|
@@ -451,8 +336,5 @@ module ClaudeSwarm
|
|
451
336
|
end
|
452
337
|
end
|
453
338
|
end
|
454
|
-
|
455
|
-
class ExecutionError < StandardError; end
|
456
|
-
class ParseError < StandardError; end
|
457
339
|
end
|
458
340
|
end
|
@@ -7,7 +7,7 @@ module ClaudeSwarm
|
|
7
7
|
attr_accessor :executor, :instance_config, :logger, :session_path, :calling_instance, :calling_instance_id
|
8
8
|
end
|
9
9
|
|
10
|
-
def initialize(instance_config, calling_instance:, calling_instance_id: nil)
|
10
|
+
def initialize(instance_config, calling_instance:, calling_instance_id: nil, debug: false)
|
11
11
|
@instance_config = instance_config
|
12
12
|
@calling_instance = calling_instance
|
13
13
|
@calling_instance_id = calling_instance_id
|
@@ -24,6 +24,7 @@ module ClaudeSwarm
|
|
24
24
|
calling_instance_id: calling_instance_id,
|
25
25
|
claude_session_id: instance_config[:claude_session_id],
|
26
26
|
additional_directories: instance_config[:directories][1..] || [],
|
27
|
+
debug: debug,
|
27
28
|
}
|
28
29
|
|
29
30
|
@executor = if instance_config[:provider] == "openai"
|
data/lib/claude_swarm/cli.rb
CHANGED
@@ -5,11 +5,11 @@ module ClaudeSwarm
|
|
5
5
|
class ChatCompletion
|
6
6
|
MAX_TURNS_WITH_TOOLS = 100_000 # virtually infinite
|
7
7
|
|
8
|
-
def initialize(openai_client:, mcp_client:, available_tools:,
|
8
|
+
def initialize(openai_client:, mcp_client:, available_tools:, executor:, instance_name:, model:, temperature: nil, reasoning_effort: nil)
|
9
9
|
@openai_client = openai_client
|
10
10
|
@mcp_client = mcp_client
|
11
11
|
@available_tools = available_tools
|
12
|
-
@executor =
|
12
|
+
@executor = executor
|
13
13
|
@instance_name = instance_name
|
14
14
|
@model = model
|
15
15
|
@temperature = temperature
|
@@ -57,7 +57,7 @@ module ClaudeSwarm
|
|
57
57
|
def process_chat_completion(messages, depth = 0)
|
58
58
|
# Prevent infinite recursion
|
59
59
|
if depth > MAX_TURNS_WITH_TOOLS
|
60
|
-
@executor.error
|
60
|
+
@executor.logger.error { "Maximum recursion depth reached in tool execution" }
|
61
61
|
return "Error: Maximum tool call depth exceeded"
|
62
62
|
end
|
63
63
|
|
@@ -83,7 +83,7 @@ module ClaudeSwarm
|
|
83
83
|
parameters[:tools] = @mcp_client.to_openai_tools if @available_tools&.any? && @mcp_client
|
84
84
|
|
85
85
|
# Log the request parameters
|
86
|
-
@executor.info
|
86
|
+
@executor.logger.info { "Chat API Request (depth=#{depth}): #{JSON.pretty_generate(parameters)}" }
|
87
87
|
|
88
88
|
# Append to session JSON
|
89
89
|
append_to_session_json({
|
@@ -97,16 +97,16 @@ module ClaudeSwarm
|
|
97
97
|
begin
|
98
98
|
response = @openai_client.chat(parameters: parameters)
|
99
99
|
rescue StandardError => e
|
100
|
-
@executor.error
|
101
|
-
@executor.error
|
100
|
+
@executor.logger.error { "Chat API error: #{e.class} - #{e.message}" }
|
101
|
+
@executor.logger.error { "Request parameters: #{JSON.pretty_generate(parameters)}" }
|
102
102
|
|
103
103
|
# Try to extract and log the response body for better debugging
|
104
104
|
if e.respond_to?(:response)
|
105
105
|
begin
|
106
106
|
error_body = e.response[:body]
|
107
|
-
@executor.error
|
107
|
+
@executor.logger.error { "Error response body: #{error_body}" }
|
108
108
|
rescue StandardError => parse_error
|
109
|
-
@executor.error
|
109
|
+
@executor.logger.error { "Could not parse error response: #{parse_error.message}" }
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
@@ -127,7 +127,7 @@ module ClaudeSwarm
|
|
127
127
|
end
|
128
128
|
|
129
129
|
# Log the response
|
130
|
-
@executor.info
|
130
|
+
@executor.logger.info { "Chat API Response (depth=#{depth}): #{JSON.pretty_generate(response)}" }
|
131
131
|
|
132
132
|
# Append to session JSON
|
133
133
|
append_to_session_json({
|
@@ -141,7 +141,7 @@ module ClaudeSwarm
|
|
141
141
|
message = response.dig("choices", 0, "message")
|
142
142
|
|
143
143
|
if message.nil?
|
144
|
-
@executor.error
|
144
|
+
@executor.logger.error { "No message in response: #{response.inspect}" }
|
145
145
|
return "Error: No response from OpenAI"
|
146
146
|
end
|
147
147
|
|
@@ -169,7 +169,7 @@ module ClaudeSwarm
|
|
169
169
|
|
170
170
|
def execute_and_append_tool_results(tool_calls, messages)
|
171
171
|
# Log tool calls
|
172
|
-
@executor.info
|
172
|
+
@executor.logger.info { "Executing tool calls: #{JSON.pretty_generate(tool_calls)}" }
|
173
173
|
|
174
174
|
# Append to session JSON
|
175
175
|
append_to_session_json({
|
@@ -189,13 +189,13 @@ module ClaudeSwarm
|
|
189
189
|
tool_args = tool_args_str.is_a?(String) ? JSON.parse(tool_args_str) : tool_args_str
|
190
190
|
|
191
191
|
# Log tool execution
|
192
|
-
@executor.info
|
192
|
+
@executor.logger.info { "Executing tool: #{tool_name} with args: #{JSON.pretty_generate(tool_args)}" }
|
193
193
|
|
194
194
|
# Execute tool via MCP
|
195
195
|
result = @mcp_client.call_tool(tool_name, tool_args)
|
196
196
|
|
197
197
|
# Log result
|
198
|
-
@executor.info
|
198
|
+
@executor.logger.info { "Tool result for #{tool_name}: #{result}" }
|
199
199
|
|
200
200
|
# Append to session JSON
|
201
201
|
append_to_session_json({
|
@@ -214,8 +214,8 @@ module ClaudeSwarm
|
|
214
214
|
content: result.to_s,
|
215
215
|
}
|
216
216
|
rescue StandardError => e
|
217
|
-
@executor.error
|
218
|
-
@executor.error
|
217
|
+
@executor.logger.error { "Tool execution failed for #{tool_name}: #{e.message}" }
|
218
|
+
@executor.logger.error { e.backtrace.join("\n") }
|
219
219
|
|
220
220
|
# Append error to session JSON
|
221
221
|
append_to_session_json({
|