swarm_memory 2.1.5 → 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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. metadata +5 -184
  4. data/lib/claude_swarm/base_executor.rb +0 -133
  5. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  6. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  7. data/lib/claude_swarm/cli.rb +0 -697
  8. data/lib/claude_swarm/commands/ps.rb +0 -215
  9. data/lib/claude_swarm/commands/show.rb +0 -139
  10. data/lib/claude_swarm/configuration.rb +0 -373
  11. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  12. data/lib/claude_swarm/json_handler.rb +0 -91
  13. data/lib/claude_swarm/mcp_generator.rb +0 -230
  14. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  15. data/lib/claude_swarm/openai/executor.rb +0 -256
  16. data/lib/claude_swarm/openai/responses.rb +0 -319
  17. data/lib/claude_swarm/orchestrator.rb +0 -878
  18. data/lib/claude_swarm/process_tracker.rb +0 -78
  19. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  20. data/lib/claude_swarm/session_path.rb +0 -42
  21. data/lib/claude_swarm/settings_generator.rb +0 -77
  22. data/lib/claude_swarm/system_utils.rb +0 -46
  23. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  24. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  25. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  27. data/lib/claude_swarm/version.rb +0 -5
  28. data/lib/claude_swarm/worktree_manager.rb +0 -475
  29. data/lib/claude_swarm/yaml_loader.rb +0 -22
  30. data/lib/claude_swarm.rb +0 -67
  31. data/lib/swarm_cli/cli.rb +0 -201
  32. data/lib/swarm_cli/command_registry.rb +0 -61
  33. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  34. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  35. data/lib/swarm_cli/commands/migrate.rb +0 -55
  36. data/lib/swarm_cli/commands/run.rb +0 -173
  37. data/lib/swarm_cli/config_loader.rb +0 -98
  38. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  39. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  40. data/lib/swarm_cli/interactive_repl.rb +0 -924
  41. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  42. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  43. data/lib/swarm_cli/migrate_options.rb +0 -54
  44. data/lib/swarm_cli/migrator.rb +0 -132
  45. data/lib/swarm_cli/options.rb +0 -151
  46. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  47. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  48. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  49. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  50. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  51. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  52. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  53. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  54. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  55. data/lib/swarm_cli/ui/icons.rb +0 -36
  56. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  57. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  58. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  59. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  60. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  61. data/lib/swarm_cli/version.rb +0 -5
  62. data/lib/swarm_cli.rb +0 -46
  63. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  64. data/lib/swarm_sdk/agent/builder.rb +0 -552
  65. data/lib/swarm_sdk/agent/chat.rb +0 -774
  66. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  67. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  68. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  69. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  70. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  71. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  72. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  73. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  75. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  76. data/lib/swarm_sdk/agent/context.rb +0 -116
  77. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  78. data/lib/swarm_sdk/agent/definition.rb +0 -477
  79. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  80. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  81. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  82. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  83. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  84. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  85. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  86. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  87. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  88. data/lib/swarm_sdk/configuration.rb +0 -135
  89. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  90. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  91. data/lib/swarm_sdk/context_compactor.rb +0 -335
  92. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  93. data/lib/swarm_sdk/context_management/context.rb +0 -328
  94. data/lib/swarm_sdk/defaults.rb +0 -196
  95. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  96. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  97. data/lib/swarm_sdk/hooks/context.rb +0 -197
  98. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  99. data/lib/swarm_sdk/hooks/error.rb +0 -29
  100. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  101. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  102. data/lib/swarm_sdk/hooks/result.rb +0 -150
  103. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  104. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  105. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  106. data/lib/swarm_sdk/log_collector.rb +0 -227
  107. data/lib/swarm_sdk/log_stream.rb +0 -127
  108. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  109. data/lib/swarm_sdk/model_aliases.json +0 -8
  110. data/lib/swarm_sdk/models.json +0 -1
  111. data/lib/swarm_sdk/models.rb +0 -120
  112. data/lib/swarm_sdk/node_context.rb +0 -245
  113. data/lib/swarm_sdk/observer/builder.rb +0 -81
  114. data/lib/swarm_sdk/observer/config.rb +0 -45
  115. data/lib/swarm_sdk/observer/manager.rb +0 -236
  116. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  117. data/lib/swarm_sdk/permissions/config.rb +0 -239
  118. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  119. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  120. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  121. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  122. data/lib/swarm_sdk/plugin.rb +0 -309
  123. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  124. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  125. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  126. data/lib/swarm_sdk/restore_result.rb +0 -65
  127. data/lib/swarm_sdk/result.rb +0 -123
  128. data/lib/swarm_sdk/snapshot.rb +0 -156
  129. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  130. data/lib/swarm_sdk/state_restorer.rb +0 -476
  131. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  132. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  133. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  134. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  135. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  136. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  141. data/lib/swarm_sdk/swarm.rb +0 -717
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/bash.rb +0 -282
  145. data/lib/swarm_sdk/tools/clock.rb +0 -44
  146. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  147. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  148. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  149. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  150. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  151. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  152. data/lib/swarm_sdk/tools/edit.rb +0 -145
  153. data/lib/swarm_sdk/tools/glob.rb +0 -166
  154. data/lib/swarm_sdk/tools/grep.rb +0 -235
  155. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  156. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  157. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -98
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/utils.rb +0 -68
  174. data/lib/swarm_sdk/validation_result.rb +0 -33
  175. data/lib/swarm_sdk/version.rb +0 -5
  176. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  177. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  178. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  179. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  180. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  181. data/lib/swarm_sdk/workflow.rb +0 -554
  182. data/lib/swarm_sdk.rb +0 -524
@@ -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