swarm_memory 2.1.4 → 2.1.6

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.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. data/lib/swarm_memory.rb +7 -2
  4. metadata +6 -185
  5. data/lib/claude_swarm/base_executor.rb +0 -133
  6. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  7. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  8. data/lib/claude_swarm/cli.rb +0 -697
  9. data/lib/claude_swarm/commands/ps.rb +0 -215
  10. data/lib/claude_swarm/commands/show.rb +0 -139
  11. data/lib/claude_swarm/configuration.rb +0 -373
  12. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  13. data/lib/claude_swarm/json_handler.rb +0 -91
  14. data/lib/claude_swarm/mcp_generator.rb +0 -243
  15. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  16. data/lib/claude_swarm/openai/executor.rb +0 -256
  17. data/lib/claude_swarm/openai/responses.rb +0 -319
  18. data/lib/claude_swarm/orchestrator.rb +0 -878
  19. data/lib/claude_swarm/process_tracker.rb +0 -78
  20. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  21. data/lib/claude_swarm/session_path.rb +0 -42
  22. data/lib/claude_swarm/settings_generator.rb +0 -77
  23. data/lib/claude_swarm/system_utils.rb +0 -46
  24. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  25. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  27. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  28. data/lib/claude_swarm/version.rb +0 -5
  29. data/lib/claude_swarm/worktree_manager.rb +0 -475
  30. data/lib/claude_swarm/yaml_loader.rb +0 -22
  31. data/lib/claude_swarm.rb +0 -67
  32. data/lib/swarm_cli/cli.rb +0 -201
  33. data/lib/swarm_cli/command_registry.rb +0 -61
  34. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  35. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  36. data/lib/swarm_cli/commands/migrate.rb +0 -55
  37. data/lib/swarm_cli/commands/run.rb +0 -173
  38. data/lib/swarm_cli/config_loader.rb +0 -98
  39. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  40. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  41. data/lib/swarm_cli/interactive_repl.rb +0 -924
  42. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  43. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  44. data/lib/swarm_cli/migrate_options.rb +0 -54
  45. data/lib/swarm_cli/migrator.rb +0 -132
  46. data/lib/swarm_cli/options.rb +0 -151
  47. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  48. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  49. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  50. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  51. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  52. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  53. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  54. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  55. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  56. data/lib/swarm_cli/ui/icons.rb +0 -36
  57. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  58. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  59. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  60. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  61. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  62. data/lib/swarm_cli/version.rb +0 -5
  63. data/lib/swarm_cli.rb +0 -46
  64. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  65. data/lib/swarm_sdk/agent/builder.rb +0 -552
  66. data/lib/swarm_sdk/agent/chat.rb +0 -774
  67. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  68. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  69. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  70. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  71. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  72. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  73. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  75. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  76. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  77. data/lib/swarm_sdk/agent/context.rb +0 -116
  78. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  79. data/lib/swarm_sdk/agent/definition.rb +0 -477
  80. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  81. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  82. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  83. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  84. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  85. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  86. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  87. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  88. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  89. data/lib/swarm_sdk/configuration.rb +0 -135
  90. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  91. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  92. data/lib/swarm_sdk/context_compactor.rb +0 -335
  93. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  94. data/lib/swarm_sdk/context_management/context.rb +0 -328
  95. data/lib/swarm_sdk/defaults.rb +0 -196
  96. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  97. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  98. data/lib/swarm_sdk/hooks/context.rb +0 -197
  99. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  100. data/lib/swarm_sdk/hooks/error.rb +0 -29
  101. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  102. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  103. data/lib/swarm_sdk/hooks/result.rb +0 -150
  104. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  105. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  106. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  107. data/lib/swarm_sdk/log_collector.rb +0 -227
  108. data/lib/swarm_sdk/log_stream.rb +0 -127
  109. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  110. data/lib/swarm_sdk/model_aliases.json +0 -8
  111. data/lib/swarm_sdk/models.json +0 -1
  112. data/lib/swarm_sdk/models.rb +0 -120
  113. data/lib/swarm_sdk/node_context.rb +0 -245
  114. data/lib/swarm_sdk/observer/builder.rb +0 -81
  115. data/lib/swarm_sdk/observer/config.rb +0 -45
  116. data/lib/swarm_sdk/observer/manager.rb +0 -236
  117. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  118. data/lib/swarm_sdk/permissions/config.rb +0 -239
  119. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  120. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  121. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  122. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  123. data/lib/swarm_sdk/plugin.rb +0 -309
  124. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  125. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  126. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  127. data/lib/swarm_sdk/restore_result.rb +0 -65
  128. data/lib/swarm_sdk/result.rb +0 -123
  129. data/lib/swarm_sdk/snapshot.rb +0 -156
  130. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  131. data/lib/swarm_sdk/state_restorer.rb +0 -476
  132. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  133. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  134. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  135. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  136. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  137. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  138. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  139. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  140. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  141. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  142. data/lib/swarm_sdk/swarm.rb +0 -717
  143. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  144. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  145. data/lib/swarm_sdk/tools/bash.rb +0 -282
  146. data/lib/swarm_sdk/tools/clock.rb +0 -44
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  160. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  161. data/lib/swarm_sdk/tools/read.rb +0 -261
  162. data/lib/swarm_sdk/tools/registry.rb +0 -205
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  166. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  167. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  168. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  169. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  170. data/lib/swarm_sdk/tools/think.rb +0 -98
  171. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  172. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  173. data/lib/swarm_sdk/tools/write.rb +0 -112
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  182. data/lib/swarm_sdk/workflow.rb +0 -554
  183. data/lib/swarm_sdk.rb +0 -524
  184. /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
@@ -1,373 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ClaudeSwarm
4
- class Configuration
5
- # Frozen constants for validation
6
- VALID_PROVIDERS = ["claude", "openai"].freeze
7
- OPENAI_SPECIFIC_FIELDS = ["temperature", "api_version", "openai_token_env", "base_url", "reasoning_effort", "zdr"].freeze
8
- VALID_API_VERSIONS = ["chat_completion", "responses"].freeze
9
- VALID_REASONING_EFFORTS = ["low", "medium", "high"].freeze
10
-
11
- # Regex patterns
12
- ENV_VAR_PATTERN = /\$\{([^}]+)\}/
13
- ENV_VAR_WITH_DEFAULT_PATTERN = /\$\{([^:}]+)(:=([^}]*))?\}/
14
- attr_reader :config, :config_path, :swarm, :swarm_name, :main_instance, :instances, :base_dir
15
-
16
- def initialize(config_path, base_dir: nil, options: {})
17
- @config_path = Pathname.new(config_path).expand_path
18
- @config_dir = @config_path.dirname
19
- @base_dir = base_dir || @config_dir.to_s
20
- @options = options
21
- load_and_validate
22
- end
23
-
24
- def main_instance_config
25
- instances[main_instance]
26
- end
27
-
28
- def instance_names
29
- instances.keys
30
- end
31
-
32
- def connections_for(instance_name)
33
- instances[instance_name][:connections] || []
34
- end
35
-
36
- def before_commands
37
- @swarm["before"] || []
38
- end
39
-
40
- def after_commands
41
- @swarm["after"] || []
42
- end
43
-
44
- def validate_directories
45
- @instances.each do |name, instance|
46
- # Validate all directories in the directories array
47
- instance[:directories].each do |directory|
48
- raise Error, "Directory '#{directory}' for instance '#{name}' does not exist" unless File.directory?(directory)
49
- end
50
- end
51
- end
52
-
53
- private
54
-
55
- def has_before_commands?
56
- @swarm && @swarm["before"] && !@swarm["before"].empty?
57
- end
58
-
59
- def load_and_validate
60
- @config = YamlLoader.load_config_file(@config_path)
61
- interpolate_env_vars!(@config)
62
- validate_version
63
- validate_swarm
64
- parse_swarm
65
- # Skip directory validation if before commands are present
66
- # They might create the directories
67
- validate_directories unless has_before_commands?
68
- end
69
-
70
- def interpolate_env_vars!(obj, path = [])
71
- case obj
72
- when String
73
- # Skip interpolation for any values inside MCP configurations
74
- # Check if we're inside an mcps array element (path like: [..., "instances", <name>, "mcps", <index>, ...])
75
- if in_mcp_config?(path)
76
- obj
77
- else
78
- interpolate_env_string(obj)
79
- end
80
- when Hash
81
- obj.each do |key, value|
82
- obj[key] = interpolate_env_vars!(value, path + [key])
83
- end
84
- obj
85
- when Array
86
- obj.map!.with_index { |v, i| interpolate_env_vars!(v, path + [i]) }
87
- else
88
- obj
89
- end
90
- end
91
-
92
- def in_mcp_config?(path)
93
- # Check if we're inside an MCP configuration
94
- # Pattern: [..., "instances", instance_name, "mcps", index, ...]
95
- return false if path.size < 4
96
-
97
- # Find the position of "mcps" in the path
98
- mcps_index = path.rindex("mcps")
99
- return false unless mcps_index
100
-
101
- # Check if this is under instances and followed by an array index
102
- return false if mcps_index < 2
103
-
104
- path[mcps_index - 2] == "instances" && path[mcps_index + 1].is_a?(Integer)
105
- end
106
-
107
- def interpolate_env_string(str)
108
- str.gsub(ENV_VAR_WITH_DEFAULT_PATTERN) do |_match|
109
- env_var = Regexp.last_match(1)
110
- has_default = Regexp.last_match(2)
111
- default_value = Regexp.last_match(3)
112
-
113
- if ENV.key?(env_var)
114
- ENV[env_var]
115
- elsif has_default
116
- default_value || ""
117
- else
118
- raise Error, "Environment variable '#{env_var}' is not set"
119
- end
120
- end
121
- end
122
-
123
- def validate_version
124
- version = @config["version"]
125
- raise Error, "Missing 'version' field in configuration" unless version
126
- raise Error, "Unsupported version: #{version}. Only version 1 is supported" unless version == 1
127
- end
128
-
129
- def validate_swarm
130
- raise Error, "Missing 'swarm' field in configuration" unless @config["swarm"]
131
-
132
- swarm = @config["swarm"]
133
- raise Error, "Missing 'name' field in swarm configuration" unless swarm["name"]
134
- raise Error, "Missing 'instances' field in swarm configuration" unless swarm["instances"]
135
- raise Error, "Missing 'main' field in swarm configuration" unless swarm["main"]
136
-
137
- raise Error, "No instances defined" if swarm["instances"].empty?
138
-
139
- main = swarm["main"]
140
- raise Error, "Main instance '#{main}' not found in instances" unless swarm["instances"].key?(main)
141
- end
142
-
143
- def parse_swarm
144
- @swarm = @config["swarm"]
145
- @swarm_name = @swarm["name"]
146
- @main_instance = @swarm["main"]
147
- @instances = {}
148
- @swarm["instances"].each do |name, config|
149
- @instances[name] = parse_instance(name, config)
150
- end
151
- validate_main_instance_provider
152
- validate_connections
153
- detect_circular_dependencies
154
- validate_openai_env_vars
155
- validate_openai_responses_api_compatibility
156
- end
157
-
158
- def parse_instance(name, config)
159
- config ||= {}
160
-
161
- # Validate required fields
162
- raise Error, "Instance '#{name}' missing required 'description' field" unless config["description"]
163
-
164
- # Parse provider (optional, defaults to claude)
165
- provider = config["provider"]
166
-
167
- # Validate provider value if specified
168
- if provider && !VALID_PROVIDERS.include?(provider)
169
- raise Error, "Instance '#{name}' has invalid provider '#{provider}'. Must be 'claude' or 'openai'"
170
- end
171
-
172
- # Validate reasoning_effort for OpenAI provider
173
- if config["reasoning_effort"]
174
- # Ensure it's only used with OpenAI provider
175
- if provider != "openai"
176
- raise Error, "Instance '#{name}' has reasoning_effort but provider is not 'openai'"
177
- end
178
-
179
- # Validate the value
180
- unless VALID_REASONING_EFFORTS.include?(config["reasoning_effort"])
181
- raise Error, "Instance '#{name}' has invalid reasoning_effort '#{config["reasoning_effort"]}'. Must be 'low', 'medium', or 'high'"
182
- end
183
- end
184
-
185
- # Validate OpenAI-specific fields only when provider is not "openai"
186
- if provider != "openai"
187
- invalid_fields = OPENAI_SPECIFIC_FIELDS & config.keys
188
- unless invalid_fields.empty?
189
- raise Error, "Instance '#{name}' has OpenAI-specific fields #{invalid_fields.join(", ")} but provider is not 'openai'"
190
- end
191
- end
192
-
193
- # Validate api_version if specified
194
- if config["api_version"] && !VALID_API_VERSIONS.include?(config["api_version"])
195
- raise Error, "Instance '#{name}' has invalid api_version '#{config["api_version"]}'. Must be 'chat_completion' or 'responses'"
196
- end
197
-
198
- # Validate tool fields are arrays if present
199
- validate_tool_field(name, config, "tools")
200
- validate_tool_field(name, config, "allowed_tools")
201
- validate_tool_field(name, config, "disallowed_tools")
202
-
203
- # Support both 'tools' (deprecated) and 'allowed_tools' for backward compatibility
204
- allowed_tools = config["allowed_tools"] || config["tools"] || []
205
-
206
- # Parse directory field - support both string and array
207
- directories = parse_directories(config["directory"])
208
-
209
- instance_config = {
210
- name: name,
211
- directory: directories.first, # Keep single directory for backward compatibility
212
- directories: directories, # New field with all directories
213
- model: config["model"] || "sonnet",
214
- connections: Array(config["connections"]),
215
- tools: Array(allowed_tools), # Keep as 'tools' internally for compatibility
216
- allowed_tools: Array(allowed_tools),
217
- disallowed_tools: Array(config["disallowed_tools"]),
218
- mcps: parse_mcps(config["mcps"] || []),
219
- prompt: config["prompt"],
220
- prompt_file: config["prompt_file"],
221
- description: config["description"],
222
- vibe: config["vibe"],
223
- worktree: parse_worktree_value(config["worktree"]),
224
- provider: provider, # nil means Claude (default)
225
- hooks: config["hooks"], # Pass hooks configuration as-is
226
- }
227
-
228
- # Add OpenAI-specific fields only when provider is "openai"
229
- if provider == "openai"
230
- instance_config[:temperature] = config["temperature"] if config["temperature"]
231
- instance_config[:api_version] = config["api_version"] || "chat_completion"
232
- instance_config[:openai_token_env] = config["openai_token_env"] || "OPENAI_API_KEY"
233
- instance_config[:base_url] = config["base_url"]
234
- instance_config[:reasoning_effort] = config["reasoning_effort"] if config["reasoning_effort"]
235
- instance_config[:zdr] = config["zdr"] if config.key?("zdr")
236
- # Default vibe to true for OpenAI instances if not specified
237
- instance_config[:vibe] = true if config["vibe"].nil?
238
- elsif config["vibe"].nil?
239
- # Default vibe to false for Claude instances if not specified
240
- instance_config[:vibe] = false
241
- end
242
-
243
- instance_config
244
- end
245
-
246
- def parse_mcps(mcps)
247
- mcps.map do |mcp|
248
- validate_mcp(mcp)
249
- mcp
250
- end
251
- end
252
-
253
- def validate_mcp(mcp)
254
- raise Error, "MCP configuration missing 'name'" unless mcp["name"]
255
-
256
- case mcp["type"]
257
- when "stdio"
258
- raise Error, "MCP '#{mcp["name"]}' missing 'command'" unless mcp["command"]
259
- when "sse", "http"
260
- raise Error, "MCP '#{mcp["name"]}' missing 'url'" unless mcp["url"]
261
- else
262
- raise Error, "Unknown MCP type '#{mcp["type"]}' for '#{mcp["name"]}'"
263
- end
264
- end
265
-
266
- def validate_connections
267
- @instances.each do |name, instance|
268
- instance[:connections].each do |connection|
269
- raise Error, "Instance '#{name}' has connection to unknown instance '#{connection}'" unless @instances.key?(connection)
270
- end
271
- end
272
- end
273
-
274
- def detect_circular_dependencies
275
- @instances.each_key do |instance_name|
276
- visited = Set.new
277
- path = []
278
- detect_cycle_from(instance_name, visited, path)
279
- end
280
- end
281
-
282
- def detect_cycle_from(instance_name, visited, path)
283
- return if visited.include?(instance_name)
284
-
285
- if path.include?(instance_name)
286
- cycle_start = path.index(instance_name)
287
- cycle = path[cycle_start..] + [instance_name]
288
- raise Error, "Circular dependency detected: #{cycle.join(" -> ")}"
289
- end
290
-
291
- path.push(instance_name)
292
- @instances[instance_name][:connections].each do |connection|
293
- detect_cycle_from(connection, visited, path)
294
- end
295
- path.pop
296
- visited.add(instance_name)
297
- end
298
-
299
- def validate_tool_field(instance_name, config, field_name)
300
- return unless config.key?(field_name)
301
-
302
- field_value = config[field_name]
303
- raise Error, "Instance '#{instance_name}' field '#{field_name}' must be an array, got #{field_value.class.name}" unless field_value.is_a?(Array)
304
- end
305
-
306
- def parse_directories(directory_config)
307
- # Default to current directory if not specified
308
- directory_config ||= "."
309
-
310
- # Convert to array and expand paths
311
- directories = Array(directory_config).map { |dir| expand_path(dir) }
312
-
313
- # Ensure at least one directory
314
- directories.empty? ? [expand_path(".")] : directories
315
- end
316
-
317
- def expand_path(path)
318
- Pathname.new(path).expand_path(@base_dir).to_s
319
- end
320
-
321
- def parse_worktree_value(value)
322
- return if value.nil? # Omitted means follow CLI behavior
323
- return value if value.is_a?(TrueClass) || value.is_a?(FalseClass)
324
- return value.to_s if value.is_a?(String) && !value.empty?
325
-
326
- raise Error, "Invalid worktree value: #{value.inspect}. Must be true, false, or a non-empty string"
327
- end
328
-
329
- def validate_openai_env_vars
330
- @instances.each_value do |instance|
331
- next unless instance[:provider] == "openai"
332
-
333
- env_var = instance[:openai_token_env]
334
- unless ENV.key?(env_var) && !ENV[env_var].to_s.strip.empty?
335
- raise Error, "Environment variable '#{env_var}' is not set. OpenAI provider instances require an API key."
336
- end
337
- end
338
- end
339
-
340
- def validate_main_instance_provider
341
- # Only validate in interactive mode (when no prompt is provided)
342
- return if @options[:prompt]
343
-
344
- main_config = @instances[@main_instance]
345
- if main_config[:provider]
346
- raise Error, "Main instance '#{@main_instance}' cannot have a provider setting in interactive mode"
347
- end
348
- end
349
-
350
- def validate_openai_responses_api_compatibility
351
- # Check if any instance uses OpenAI provider with responses API
352
- responses_api_instances = @instances.select do |_name, instance|
353
- instance[:provider] == "openai" && instance[:api_version] == "responses"
354
- end
355
-
356
- return if responses_api_instances.empty?
357
-
358
- # Check ruby-openai version
359
- begin
360
- require "openai/version"
361
- openai_version = Gem::Version.new(::OpenAI::VERSION)
362
- required_version = Gem::Version.new("8.0.0")
363
-
364
- if openai_version < required_version
365
- instance_names = responses_api_instances.keys.join(", ")
366
- raise Error, "Instances #{instance_names} use OpenAI provider with api_version 'responses', which requires ruby-openai >= 8.0. Current version is #{openai_version}. Please update your Gemfile or run: gem install ruby-openai -v '>= 8.0'"
367
- end
368
- rescue LoadError
369
- # ruby-openai is not installed, which is fine - it will be caught later when trying to use it
370
- end
371
- end
372
- end
373
- end
@@ -1,42 +0,0 @@
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,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ClaudeSwarm
4
- # Centralized JSON handling for the Claude Swarm codebase
5
- class JsonHandler
6
- class << self
7
- # Parse JSON string into Ruby object
8
- # @param json_string [String] The JSON string to parse
9
- # @param raise_on_error [Boolean] Whether to raise exception on error (default: false)
10
- # @return [Object] The parsed Ruby object, or original string if parsing fails and raise_on_error is false
11
- # @raise [JSON::ParserError] If the JSON is invalid and raise_on_error is true
12
- def parse(json_string, raise_on_error: false)
13
- JSON.parse(json_string)
14
- rescue JSON::ParserError => e
15
- raise e if raise_on_error
16
-
17
- json_string
18
- end
19
-
20
- # Parse JSON string with exception raising
21
- # @param json_string [String] The JSON string to parse
22
- # @return [Object] The parsed Ruby object
23
- # @raise [JSON::ParserError] If the JSON is invalid
24
- def parse!(json_string)
25
- parse(json_string, raise_on_error: true)
26
- end
27
-
28
- # Parse JSON from a file with exception raising
29
- # @param file_path [String] Path to the JSON file
30
- # @return [Object] The parsed Ruby object
31
- # @raise [Errno::ENOENT] If the file does not exist
32
- # @raise [JSON::ParserError] If the file contains invalid JSON
33
- def parse_file!(file_path)
34
- content = File.read(file_path)
35
- parse!(content)
36
- end
37
-
38
- # Parse JSON from a file, returning nil on error
39
- # @param file_path [String] Path to the JSON file
40
- # @return [Object, nil] The parsed Ruby object or nil if file doesn't exist or contains invalid JSON
41
- def parse_file(file_path)
42
- parse_file!(file_path)
43
- rescue Errno::ENOENT, JSON::ParserError
44
- nil
45
- end
46
-
47
- # Generate pretty-formatted JSON string
48
- # @param object [Object] The Ruby object to convert to JSON
49
- # @param raise_on_error [Boolean] Whether to raise exception on error (default: false)
50
- # @return [String, nil] The pretty-formatted JSON string, or nil if generation fails and raise_on_error is false
51
- # @raise [JSON::GeneratorError] If the object cannot be converted to JSON and raise_on_error is true
52
- def pretty_generate(object, raise_on_error: false)
53
- JSON.pretty_generate(object)
54
- rescue JSON::GeneratorError, JSON::NestingError => e
55
- raise e if raise_on_error
56
-
57
- nil
58
- end
59
-
60
- # Generate pretty-formatted JSON string with exception raising
61
- # @param object [Object] The Ruby object to convert to JSON
62
- # @return [String] The pretty-formatted JSON string
63
- # @raise [JSON::GeneratorError] If the object cannot be converted to JSON
64
- def pretty_generate!(object)
65
- pretty_generate(object, raise_on_error: true)
66
- end
67
-
68
- # Write Ruby object to a JSON file with pretty formatting
69
- # @param file_path [String] Path to the JSON file
70
- # @param object [Object] The Ruby object to write
71
- # @return [Boolean] True if successful, false if generation or write fails
72
- def write_file(file_path, object)
73
- json_string = pretty_generate!(object)
74
- File.write(file_path, json_string)
75
- true
76
- rescue JSON::GeneratorError, JSON::NestingError, SystemCallError
77
- false
78
- end
79
-
80
- # Write Ruby object to a JSON file with exception raising
81
- # @param file_path [String] Path to the JSON file
82
- # @param object [Object] The Ruby object to write
83
- # @raise [JSON::GeneratorError] If the object cannot be converted to JSON
84
- # @raise [SystemCallError] If the file cannot be written
85
- def write_file!(file_path, object)
86
- json_string = pretty_generate!(object)
87
- File.write(file_path, json_string)
88
- end
89
- end
90
- end
91
- end