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.
- checksums.yaml +4 -4
- data/lib/swarm_memory/version.rb +1 -1
- metadata +5 -184
- data/lib/claude_swarm/base_executor.rb +0 -133
- data/lib/claude_swarm/claude_code_executor.rb +0 -349
- data/lib/claude_swarm/claude_mcp_server.rb +0 -78
- data/lib/claude_swarm/cli.rb +0 -697
- data/lib/claude_swarm/commands/ps.rb +0 -215
- data/lib/claude_swarm/commands/show.rb +0 -139
- data/lib/claude_swarm/configuration.rb +0 -373
- data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
- data/lib/claude_swarm/json_handler.rb +0 -91
- data/lib/claude_swarm/mcp_generator.rb +0 -230
- data/lib/claude_swarm/openai/chat_completion.rb +0 -256
- data/lib/claude_swarm/openai/executor.rb +0 -256
- data/lib/claude_swarm/openai/responses.rb +0 -319
- data/lib/claude_swarm/orchestrator.rb +0 -878
- data/lib/claude_swarm/process_tracker.rb +0 -78
- data/lib/claude_swarm/session_cost_calculator.rb +0 -209
- data/lib/claude_swarm/session_path.rb +0 -42
- data/lib/claude_swarm/settings_generator.rb +0 -77
- data/lib/claude_swarm/system_utils.rb +0 -46
- data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
- data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
- data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
- data/lib/claude_swarm/tools/task_tool.rb +0 -63
- data/lib/claude_swarm/version.rb +0 -5
- data/lib/claude_swarm/worktree_manager.rb +0 -475
- data/lib/claude_swarm/yaml_loader.rb +0 -22
- data/lib/claude_swarm.rb +0 -67
- data/lib/swarm_cli/cli.rb +0 -201
- data/lib/swarm_cli/command_registry.rb +0 -61
- data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
- data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
- data/lib/swarm_cli/commands/migrate.rb +0 -55
- data/lib/swarm_cli/commands/run.rb +0 -173
- data/lib/swarm_cli/config_loader.rb +0 -98
- data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
- data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
- data/lib/swarm_cli/interactive_repl.rb +0 -924
- data/lib/swarm_cli/mcp_serve_options.rb +0 -44
- data/lib/swarm_cli/mcp_tools_options.rb +0 -59
- data/lib/swarm_cli/migrate_options.rb +0 -54
- data/lib/swarm_cli/migrator.rb +0 -132
- data/lib/swarm_cli/options.rb +0 -151
- data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
- data/lib/swarm_cli/ui/components/content_block.rb +0 -120
- data/lib/swarm_cli/ui/components/divider.rb +0 -57
- data/lib/swarm_cli/ui/components/panel.rb +0 -62
- data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
- data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
- data/lib/swarm_cli/ui/formatters/number.rb +0 -58
- data/lib/swarm_cli/ui/formatters/text.rb +0 -77
- data/lib/swarm_cli/ui/formatters/time.rb +0 -73
- data/lib/swarm_cli/ui/icons.rb +0 -36
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
- data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
- data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
- data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
- data/lib/swarm_cli/version.rb +0 -5
- data/lib/swarm_cli.rb +0 -46
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
- data/lib/swarm_sdk/agent/builder.rb +0 -552
- data/lib/swarm_sdk/agent/chat.rb +0 -774
- data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
- data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
- data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
- data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
- data/lib/swarm_sdk/agent/context.rb +0 -116
- data/lib/swarm_sdk/agent/context_manager.rb +0 -315
- data/lib/swarm_sdk/agent/definition.rb +0 -477
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
- data/lib/swarm_sdk/builders/base_builder.rb +0 -409
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
- data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
- data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
- data/lib/swarm_sdk/concerns/validatable.rb +0 -55
- data/lib/swarm_sdk/configuration/parser.rb +0 -353
- data/lib/swarm_sdk/configuration/translator.rb +0 -255
- data/lib/swarm_sdk/configuration.rb +0 -135
- data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
- data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
- data/lib/swarm_sdk/context_compactor.rb +0 -335
- data/lib/swarm_sdk/context_management/builder.rb +0 -128
- data/lib/swarm_sdk/context_management/context.rb +0 -328
- data/lib/swarm_sdk/defaults.rb +0 -196
- data/lib/swarm_sdk/events_to_messages.rb +0 -199
- data/lib/swarm_sdk/hooks/adapter.rb +0 -359
- data/lib/swarm_sdk/hooks/context.rb +0 -197
- data/lib/swarm_sdk/hooks/definition.rb +0 -80
- data/lib/swarm_sdk/hooks/error.rb +0 -29
- data/lib/swarm_sdk/hooks/executor.rb +0 -146
- data/lib/swarm_sdk/hooks/registry.rb +0 -147
- data/lib/swarm_sdk/hooks/result.rb +0 -150
- data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
- data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
- data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
- data/lib/swarm_sdk/log_collector.rb +0 -227
- data/lib/swarm_sdk/log_stream.rb +0 -127
- data/lib/swarm_sdk/markdown_parser.rb +0 -75
- data/lib/swarm_sdk/model_aliases.json +0 -8
- data/lib/swarm_sdk/models.json +0 -1
- data/lib/swarm_sdk/models.rb +0 -120
- data/lib/swarm_sdk/node_context.rb +0 -245
- data/lib/swarm_sdk/observer/builder.rb +0 -81
- data/lib/swarm_sdk/observer/config.rb +0 -45
- data/lib/swarm_sdk/observer/manager.rb +0 -236
- data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
- data/lib/swarm_sdk/permissions/config.rb +0 -239
- data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
- data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
- data/lib/swarm_sdk/permissions/validator.rb +0 -173
- data/lib/swarm_sdk/permissions_builder.rb +0 -122
- data/lib/swarm_sdk/plugin.rb +0 -309
- data/lib/swarm_sdk/plugin_registry.rb +0 -101
- data/lib/swarm_sdk/proc_helpers.rb +0 -53
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
- data/lib/swarm_sdk/restore_result.rb +0 -65
- data/lib/swarm_sdk/result.rb +0 -123
- data/lib/swarm_sdk/snapshot.rb +0 -156
- data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
- data/lib/swarm_sdk/state_restorer.rb +0 -476
- data/lib/swarm_sdk/state_snapshot.rb +0 -334
- data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
- data/lib/swarm_sdk/swarm/builder.rb +0 -249
- data/lib/swarm_sdk/swarm/executor.rb +0 -213
- data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
- data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
- data/lib/swarm_sdk/swarm.rb +0 -717
- data/lib/swarm_sdk/swarm_loader.rb +0 -145
- data/lib/swarm_sdk/swarm_registry.rb +0 -136
- data/lib/swarm_sdk/tools/bash.rb +0 -282
- data/lib/swarm_sdk/tools/clock.rb +0 -44
- data/lib/swarm_sdk/tools/delegate.rb +0 -267
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
- data/lib/swarm_sdk/tools/edit.rb +0 -145
- data/lib/swarm_sdk/tools/glob.rb +0 -166
- data/lib/swarm_sdk/tools/grep.rb +0 -235
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
- data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
- data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
- data/lib/swarm_sdk/tools/read.rb +0 -261
- data/lib/swarm_sdk/tools/registry.rb +0 -205
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
- data/lib/swarm_sdk/tools/think.rb +0 -98
- data/lib/swarm_sdk/tools/todo_write.rb +0 -235
- data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
- data/lib/swarm_sdk/tools/write.rb +0 -112
- data/lib/swarm_sdk/utils.rb +0 -68
- data/lib/swarm_sdk/validation_result.rb +0 -33
- data/lib/swarm_sdk/version.rb +0 -5
- data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
- data/lib/swarm_sdk/workflow/builder.rb +0 -143
- data/lib/swarm_sdk/workflow/executor.rb +0 -497
- data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
- data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
- data/lib/swarm_sdk/workflow.rb +0 -554
- data/lib/swarm_sdk.rb +0 -524
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module Tools
|
|
5
|
-
# Delegate tool for working with other agents in the swarm
|
|
6
|
-
#
|
|
7
|
-
# Creates agent-specific collaboration tools (e.g., WorkWithBackend)
|
|
8
|
-
# that allow one agent to work with another agent.
|
|
9
|
-
# Supports pre/post delegation hooks for customization.
|
|
10
|
-
class Delegate < RubyLLM::Tool
|
|
11
|
-
# Tool name prefix for delegation tools
|
|
12
|
-
# Change this to customize the tool naming pattern (e.g., "DelegateTaskTo", "AskAgent", etc.)
|
|
13
|
-
TOOL_NAME_PREFIX = "WorkWith"
|
|
14
|
-
|
|
15
|
-
class << self
|
|
16
|
-
# Generate tool name for a delegate agent
|
|
17
|
-
#
|
|
18
|
-
# This is the single source of truth for delegation tool naming.
|
|
19
|
-
# Used both when creating Delegate instances and when predicting tool names
|
|
20
|
-
# for agent context setup.
|
|
21
|
-
#
|
|
22
|
-
# @param delegate_name [String, Symbol] Name of the delegate agent
|
|
23
|
-
# @return [String] Tool name (e.g., "WorkWithBackend")
|
|
24
|
-
def tool_name_for(delegate_name)
|
|
25
|
-
"#{TOOL_NAME_PREFIX}#{delegate_name.to_s.capitalize}"
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
attr_reader :delegate_name, :delegate_target, :tool_name
|
|
30
|
-
|
|
31
|
-
# Initialize a delegation tool
|
|
32
|
-
#
|
|
33
|
-
# @param delegate_name [String] Name of the delegate agent (e.g., "backend")
|
|
34
|
-
# @param delegate_description [String] Description of the delegate agent
|
|
35
|
-
# @param delegate_chat [AgentChat, nil] The chat instance for the delegate agent (nil if delegating to swarm)
|
|
36
|
-
# @param agent_name [Symbol, String] Name of the agent using this tool
|
|
37
|
-
# @param swarm [Swarm] The swarm instance (provides hook_registry, delegation_call_stack, swarm_registry)
|
|
38
|
-
# @param delegating_chat [Agent::Chat, nil] The chat instance of the agent doing the delegating (for accessing hooks)
|
|
39
|
-
def initialize(
|
|
40
|
-
delegate_name:,
|
|
41
|
-
delegate_description:,
|
|
42
|
-
delegate_chat:,
|
|
43
|
-
agent_name:,
|
|
44
|
-
swarm:,
|
|
45
|
-
delegating_chat: nil
|
|
46
|
-
)
|
|
47
|
-
super()
|
|
48
|
-
|
|
49
|
-
@delegate_name = delegate_name
|
|
50
|
-
@delegate_description = delegate_description
|
|
51
|
-
@delegate_chat = delegate_chat
|
|
52
|
-
@agent_name = agent_name
|
|
53
|
-
@swarm = swarm
|
|
54
|
-
@delegating_chat = delegating_chat
|
|
55
|
-
|
|
56
|
-
# Generate tool name using canonical method
|
|
57
|
-
@tool_name = self.class.tool_name_for(delegate_name)
|
|
58
|
-
@delegate_target = delegate_name.to_s
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Override description to return dynamic string based on delegate
|
|
62
|
-
def description
|
|
63
|
-
"Work with #{@delegate_name} to delegate work, ask questions, or collaborate. #{@delegate_description}"
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
param :message,
|
|
67
|
-
type: "string",
|
|
68
|
-
desc: "Message to send to the agent - can be a work request, question, or collaboration message",
|
|
69
|
-
required: true
|
|
70
|
-
|
|
71
|
-
# Override name to return custom delegation tool name
|
|
72
|
-
def name
|
|
73
|
-
@tool_name
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Execute delegation with pre/post hooks
|
|
77
|
-
#
|
|
78
|
-
# @param message [String] Message to send to the agent
|
|
79
|
-
# @return [String] Result from delegate agent or error message
|
|
80
|
-
def execute(message:)
|
|
81
|
-
# Access swarm infrastructure
|
|
82
|
-
call_stack = @swarm.delegation_call_stack
|
|
83
|
-
hook_registry = @swarm.hook_registry
|
|
84
|
-
swarm_registry = @swarm.swarm_registry
|
|
85
|
-
|
|
86
|
-
# Check for circular dependency
|
|
87
|
-
if call_stack.include?(@delegate_target)
|
|
88
|
-
emit_circular_warning(call_stack)
|
|
89
|
-
return "Error: Circular delegation detected: #{call_stack.join(" -> ")} -> #{@delegate_target}. " \
|
|
90
|
-
"Please restructure your delegation to avoid infinite loops."
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Get agent-specific hooks from the delegating chat instance
|
|
94
|
-
agent_hooks = if @delegating_chat&.respond_to?(:hook_agent_hooks)
|
|
95
|
-
@delegating_chat.hook_agent_hooks || {}
|
|
96
|
-
else
|
|
97
|
-
{}
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Trigger pre_delegation callback
|
|
101
|
-
context = Hooks::Context.new(
|
|
102
|
-
event: :pre_delegation,
|
|
103
|
-
agent_name: @agent_name,
|
|
104
|
-
swarm: @swarm,
|
|
105
|
-
delegation_target: @delegate_target,
|
|
106
|
-
metadata: {
|
|
107
|
-
tool_name: @tool_name,
|
|
108
|
-
message: message,
|
|
109
|
-
timestamp: Time.now.utc.iso8601,
|
|
110
|
-
},
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
executor = Hooks::Executor.new(hook_registry, logger: RubyLLM.logger)
|
|
114
|
-
pre_agent_hooks = agent_hooks[:pre_delegation] || []
|
|
115
|
-
result = executor.execute_safe(event: :pre_delegation, context: context, callbacks: pre_agent_hooks)
|
|
116
|
-
|
|
117
|
-
# Check if callback halted or replaced the delegation
|
|
118
|
-
if result.halt?
|
|
119
|
-
return result.value || "Delegation halted by callback"
|
|
120
|
-
elsif result.replace?
|
|
121
|
-
return result.value
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Determine delegation type and proceed
|
|
125
|
-
delegation_result = if @delegate_chat
|
|
126
|
-
# Delegate to agent
|
|
127
|
-
delegate_to_agent(message, call_stack)
|
|
128
|
-
elsif swarm_registry&.registered?(@delegate_target)
|
|
129
|
-
# Delegate to registered swarm
|
|
130
|
-
delegate_to_swarm(message, call_stack, swarm_registry)
|
|
131
|
-
else
|
|
132
|
-
raise ConfigurationError, "Unknown delegation target: #{@delegate_target}"
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Trigger post_delegation callback
|
|
136
|
-
post_context = Hooks::Context.new(
|
|
137
|
-
event: :post_delegation,
|
|
138
|
-
agent_name: @agent_name,
|
|
139
|
-
swarm: @swarm,
|
|
140
|
-
delegation_target: @delegate_target,
|
|
141
|
-
delegation_result: delegation_result,
|
|
142
|
-
metadata: {
|
|
143
|
-
tool_name: @tool_name,
|
|
144
|
-
message: message,
|
|
145
|
-
result: delegation_result,
|
|
146
|
-
timestamp: Time.now.utc.iso8601,
|
|
147
|
-
},
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
post_agent_hooks = agent_hooks[:post_delegation] || []
|
|
151
|
-
post_result = executor.execute_safe(event: :post_delegation, context: post_context, callbacks: post_agent_hooks)
|
|
152
|
-
|
|
153
|
-
# Return modified result if callback replaces it
|
|
154
|
-
if post_result.replace?
|
|
155
|
-
post_result.value
|
|
156
|
-
else
|
|
157
|
-
delegation_result
|
|
158
|
-
end
|
|
159
|
-
rescue Faraday::TimeoutError, Net::ReadTimeout => e
|
|
160
|
-
# Log timeout error as JSON event
|
|
161
|
-
LogStream.emit(
|
|
162
|
-
type: "delegation_error",
|
|
163
|
-
agent: @agent_name,
|
|
164
|
-
swarm_id: @swarm.swarm_id,
|
|
165
|
-
parent_swarm_id: @swarm.parent_swarm_id,
|
|
166
|
-
delegate_to: @tool_name,
|
|
167
|
-
error_class: e.class.name,
|
|
168
|
-
error_message: "Request timed out",
|
|
169
|
-
error_backtrace: e.backtrace&.first(5) || [],
|
|
170
|
-
)
|
|
171
|
-
"Error: Request to #{@tool_name} timed out. The agent may be overloaded or the LLM service is not responding. Please try again or simplify the task."
|
|
172
|
-
rescue Faraday::Error => e
|
|
173
|
-
# Log network error as JSON event
|
|
174
|
-
LogStream.emit(
|
|
175
|
-
type: "delegation_error",
|
|
176
|
-
agent: @agent_name,
|
|
177
|
-
swarm_id: @swarm.swarm_id,
|
|
178
|
-
parent_swarm_id: @swarm.parent_swarm_id,
|
|
179
|
-
delegate_to: @tool_name,
|
|
180
|
-
error_class: e.class.name,
|
|
181
|
-
error_message: e.message,
|
|
182
|
-
error_backtrace: e.backtrace&.first(5) || [],
|
|
183
|
-
)
|
|
184
|
-
"Error: Network error communicating with #{@tool_name}: #{e.class.name}. Please check connectivity and try again."
|
|
185
|
-
rescue StandardError => e
|
|
186
|
-
# Log unexpected error as JSON event
|
|
187
|
-
backtrace_array = e.backtrace&.first(5) || []
|
|
188
|
-
LogStream.emit(
|
|
189
|
-
type: "delegation_error",
|
|
190
|
-
agent: @agent_name,
|
|
191
|
-
swarm_id: @swarm.swarm_id,
|
|
192
|
-
parent_swarm_id: @swarm.parent_swarm_id,
|
|
193
|
-
delegate_to: @tool_name,
|
|
194
|
-
error_class: e.class.name,
|
|
195
|
-
error_message: e.message,
|
|
196
|
-
error_backtrace: backtrace_array,
|
|
197
|
-
)
|
|
198
|
-
# Return error string for LLM
|
|
199
|
-
backtrace_str = backtrace_array.join("\n ")
|
|
200
|
-
"Error: #{@tool_name} encountered an error: #{e.class.name}: #{e.message}\nBacktrace:\n #{backtrace_str}"
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
private
|
|
204
|
-
|
|
205
|
-
# Delegate to an agent
|
|
206
|
-
#
|
|
207
|
-
# @param message [String] Message to send to the agent
|
|
208
|
-
# @param call_stack [Array] Delegation call stack for circular dependency detection
|
|
209
|
-
# @return [String] Result from agent
|
|
210
|
-
def delegate_to_agent(message, call_stack)
|
|
211
|
-
# Push delegate target onto call stack to track delegation chain
|
|
212
|
-
call_stack.push(@delegate_target)
|
|
213
|
-
begin
|
|
214
|
-
response = @delegate_chat.ask(message, source: "delegation")
|
|
215
|
-
response.content
|
|
216
|
-
ensure
|
|
217
|
-
# Always pop from stack, even if delegation fails
|
|
218
|
-
call_stack.pop
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Delegate to a registered swarm
|
|
223
|
-
#
|
|
224
|
-
# @param message [String] Message to send to the swarm
|
|
225
|
-
# @param call_stack [Array] Delegation call stack for circular dependency detection
|
|
226
|
-
# @param swarm_registry [SwarmRegistry] Registry for sub-swarms
|
|
227
|
-
# @return [String] Result from swarm's lead agent
|
|
228
|
-
def delegate_to_swarm(message, call_stack, swarm_registry)
|
|
229
|
-
# Load sub-swarm (lazy load + cache)
|
|
230
|
-
subswarm = swarm_registry.load_swarm(@delegate_target)
|
|
231
|
-
|
|
232
|
-
# Push delegate target onto call stack to track delegation chain
|
|
233
|
-
call_stack.push(@delegate_target)
|
|
234
|
-
begin
|
|
235
|
-
# Execute sub-swarm's lead agent
|
|
236
|
-
lead_agent = subswarm.agents[subswarm.lead_agent]
|
|
237
|
-
response = lead_agent.ask(message, source: "delegation")
|
|
238
|
-
result = response.content
|
|
239
|
-
|
|
240
|
-
# Reset if keep_context: false
|
|
241
|
-
swarm_registry.reset_if_needed(@delegate_target)
|
|
242
|
-
|
|
243
|
-
result
|
|
244
|
-
ensure
|
|
245
|
-
# Always pop from stack, even if delegation fails
|
|
246
|
-
call_stack.pop
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
# Emit circular dependency warning event
|
|
251
|
-
#
|
|
252
|
-
# @param call_stack [Array] Current delegation call stack
|
|
253
|
-
# @return [void]
|
|
254
|
-
def emit_circular_warning(call_stack)
|
|
255
|
-
LogStream.emit(
|
|
256
|
-
type: "delegation_circular_dependency",
|
|
257
|
-
agent: @agent_name,
|
|
258
|
-
swarm_id: @swarm.swarm_id,
|
|
259
|
-
parent_swarm_id: @swarm.parent_swarm_id,
|
|
260
|
-
target: @delegate_target,
|
|
261
|
-
call_stack: call_stack,
|
|
262
|
-
timestamp: Time.now.utc.iso8601,
|
|
263
|
-
)
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
end
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module Tools
|
|
5
|
-
module DocumentConverters
|
|
6
|
-
# Base class for document converters
|
|
7
|
-
# Provides common interface and utility methods for converting various document formats
|
|
8
|
-
class BaseConverter
|
|
9
|
-
class << self
|
|
10
|
-
# The gem name required for this converter
|
|
11
|
-
# @return [String]
|
|
12
|
-
def gem_name
|
|
13
|
-
raise NotImplementedError, "#{name} must implement .gem_name"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Human-readable format name
|
|
17
|
-
# @return [String]
|
|
18
|
-
def format_name
|
|
19
|
-
raise NotImplementedError, "#{name} must implement .format_name"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# File extensions this converter handles
|
|
23
|
-
# @return [Array<String>]
|
|
24
|
-
def extensions
|
|
25
|
-
raise NotImplementedError, "#{name} must implement .extensions"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Check if the required gem is available
|
|
29
|
-
# @return [Boolean]
|
|
30
|
-
def available?
|
|
31
|
-
gem_available?(gem_name)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Check if a gem is installed
|
|
35
|
-
# @param gem_name [String] Name of the gem to check
|
|
36
|
-
# @return [Boolean]
|
|
37
|
-
def gem_available?(gem_name)
|
|
38
|
-
Gem::Specification.find_by_name(gem_name)
|
|
39
|
-
true
|
|
40
|
-
rescue Gem::LoadError
|
|
41
|
-
false
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Convert a document file to text/content
|
|
46
|
-
# @param file_path [String] Path to the file
|
|
47
|
-
# @return [String, RubyLLM::Content] Converted content or error message
|
|
48
|
-
def convert(file_path)
|
|
49
|
-
raise NotImplementedError, "#{self.class.name} must implement #convert"
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
protected
|
|
53
|
-
|
|
54
|
-
# Return a system reminder about missing gem
|
|
55
|
-
# @param format [String] Format name (e.g., "PDF")
|
|
56
|
-
# @param gem_name [String] Required gem name
|
|
57
|
-
# @return [String]
|
|
58
|
-
def unsupported_format_reminder(format, gem_name)
|
|
59
|
-
<<~REMINDER
|
|
60
|
-
<system-reminder>
|
|
61
|
-
This file is a #{format} document, but the required gem is not installed.
|
|
62
|
-
|
|
63
|
-
To enable #{format} file reading, please install the gem:
|
|
64
|
-
gem install #{gem_name}
|
|
65
|
-
|
|
66
|
-
Or add to your Gemfile:
|
|
67
|
-
gem "#{gem_name}"
|
|
68
|
-
|
|
69
|
-
Don't install the gem yourself. Ask the user if they would like you to install this gem.
|
|
70
|
-
</system-reminder>
|
|
71
|
-
REMINDER
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Return an error message
|
|
75
|
-
# @param message [String] Error message
|
|
76
|
-
# @return [String]
|
|
77
|
-
def error(message)
|
|
78
|
-
"Error: #{message}"
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module Tools
|
|
5
|
-
module DocumentConverters
|
|
6
|
-
# Converts DOCX documents to text with image extraction
|
|
7
|
-
class DocxConverter < BaseConverter
|
|
8
|
-
class << self
|
|
9
|
-
def gem_name
|
|
10
|
-
"docx"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def format_name
|
|
14
|
-
"DOCX"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def extensions
|
|
18
|
-
[".docx", ".doc"]
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Convert a DOCX document to text/content
|
|
23
|
-
# @param file_path [String] Path to the DOCX file
|
|
24
|
-
# @return [String, RubyLLM::Content] Converted content or error message
|
|
25
|
-
def convert(file_path)
|
|
26
|
-
unless self.class.available?
|
|
27
|
-
return unsupported_format_reminder(self.class.format_name, self.class.gem_name)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Check for legacy DOC format
|
|
31
|
-
if File.extname(file_path).downcase == ".doc"
|
|
32
|
-
return error("DOC format is not supported. Please convert to DOCX first.")
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
begin
|
|
36
|
-
require "docx"
|
|
37
|
-
require "tmpdir"
|
|
38
|
-
|
|
39
|
-
doc = Docx::Document.open(file_path)
|
|
40
|
-
|
|
41
|
-
# Extract images from the DOCX
|
|
42
|
-
image_paths = ImageExtractors::DocxImageExtractor.extract_images(doc, file_path)
|
|
43
|
-
|
|
44
|
-
output = []
|
|
45
|
-
output << "Document: #{File.basename(file_path)}"
|
|
46
|
-
output << "=" * 60
|
|
47
|
-
output << ""
|
|
48
|
-
|
|
49
|
-
# Extract paragraphs
|
|
50
|
-
paragraphs = doc.paragraphs.map(&:text).reject(&:empty?)
|
|
51
|
-
|
|
52
|
-
# Check for empty document
|
|
53
|
-
if paragraphs.empty? && doc.tables.empty?
|
|
54
|
-
output << "(Document is empty - no paragraphs or tables)"
|
|
55
|
-
else
|
|
56
|
-
output += paragraphs
|
|
57
|
-
|
|
58
|
-
# Extract tables with enhanced formatting
|
|
59
|
-
if doc.tables.any?
|
|
60
|
-
output << ""
|
|
61
|
-
output << "Tables:"
|
|
62
|
-
output << "-" * 60
|
|
63
|
-
|
|
64
|
-
doc.tables.each_with_index do |table, idx|
|
|
65
|
-
output << ""
|
|
66
|
-
output << "Table #{idx + 1} (#{table.row_count} rows × #{table.column_count} columns):"
|
|
67
|
-
|
|
68
|
-
table.rows.each do |row|
|
|
69
|
-
output << row.cells.map(&:text).join(" | ")
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
text_content = output.join("\n")
|
|
76
|
-
|
|
77
|
-
# If there are images, return Content with attachments
|
|
78
|
-
if image_paths.any?
|
|
79
|
-
content = RubyLLM::Content.new(text_content)
|
|
80
|
-
image_paths.each do |image_path|
|
|
81
|
-
content.add_attachment(image_path)
|
|
82
|
-
end
|
|
83
|
-
content
|
|
84
|
-
else
|
|
85
|
-
# No images, return just text
|
|
86
|
-
text_content
|
|
87
|
-
end
|
|
88
|
-
rescue Zip::Error => e
|
|
89
|
-
error("Invalid or corrupted DOCX file: #{e.message}")
|
|
90
|
-
rescue Errno::ENOENT => e
|
|
91
|
-
error("File not found or missing document.xml: #{e.message}")
|
|
92
|
-
rescue StandardError => e
|
|
93
|
-
error("Failed to parse DOCX file: #{e.message}")
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module Tools
|
|
5
|
-
module DocumentConverters
|
|
6
|
-
# Converter for HTML to Markdown
|
|
7
|
-
# Uses reverse_markdown gem if available, otherwise falls back to simple regex-based conversion
|
|
8
|
-
class HtmlConverter < BaseConverter
|
|
9
|
-
class << self
|
|
10
|
-
def gem_name
|
|
11
|
-
"reverse_markdown"
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def format_name
|
|
15
|
-
"HTML"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def extensions
|
|
19
|
-
[".html", ".htm"]
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Convert HTML string to Markdown
|
|
24
|
-
# @param html [String] HTML content to convert
|
|
25
|
-
# @return [String] Markdown content
|
|
26
|
-
def convert_string(html)
|
|
27
|
-
if self.class.available?
|
|
28
|
-
convert_with_gem(html)
|
|
29
|
-
else
|
|
30
|
-
convert_simple(html)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Convert HTML file to Markdown
|
|
35
|
-
# @param file_path [String] Path to HTML file
|
|
36
|
-
# @return [String] Markdown content
|
|
37
|
-
def convert(file_path)
|
|
38
|
-
html = File.read(file_path)
|
|
39
|
-
convert_string(html)
|
|
40
|
-
rescue StandardError => e
|
|
41
|
-
error("Failed to read HTML file: #{e.message}")
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
# Convert HTML to Markdown using reverse_markdown gem
|
|
47
|
-
# @param html [String] HTML content
|
|
48
|
-
# @return [String] Markdown content
|
|
49
|
-
def convert_with_gem(html)
|
|
50
|
-
require "reverse_markdown"
|
|
51
|
-
|
|
52
|
-
ReverseMarkdown.convert(html, unknown_tags: :bypass, github_flavored: true)
|
|
53
|
-
rescue StandardError
|
|
54
|
-
# Fallback to simple conversion if gem conversion fails
|
|
55
|
-
convert_simple(html)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Simple regex-based HTML to Markdown conversion (fallback)
|
|
59
|
-
# @param html [String] HTML content
|
|
60
|
-
# @return [String] Markdown content
|
|
61
|
-
def convert_simple(html)
|
|
62
|
-
# Remove script and style tags
|
|
63
|
-
content = html.gsub(%r{<script[^>]*>.*?</script>}im, "")
|
|
64
|
-
content = content.gsub(%r{<style[^>]*>.*?</style>}im, "")
|
|
65
|
-
|
|
66
|
-
# Convert common HTML elements
|
|
67
|
-
content = content.gsub(%r{<h1[^>]*>(.*?)</h1>}im, "\n# \\1\n")
|
|
68
|
-
content = content.gsub(%r{<h2[^>]*>(.*?)</h2>}im, "\n## \\1\n")
|
|
69
|
-
content = content.gsub(%r{<h3[^>]*>(.*?)</h3>}im, "\n### \\1\n")
|
|
70
|
-
content = content.gsub(%r{<h4[^>]*>(.*?)</h4>}im, "\n#### \\1\n")
|
|
71
|
-
content = content.gsub(%r{<h5[^>]*>(.*?)</h5>}im, "\n##### \\1\n")
|
|
72
|
-
content = content.gsub(%r{<h6[^>]*>(.*?)</h6>}im, "\n###### \\1\n")
|
|
73
|
-
content = content.gsub(%r{<p[^>]*>(.*?)</p>}im, "\n\\1\n")
|
|
74
|
-
content = content.gsub(%r{<br\s*/?>}i, "\n")
|
|
75
|
-
content = content.gsub(%r{<strong[^>]*>(.*?)</strong>}im, "**\\1**")
|
|
76
|
-
content = content.gsub(%r{<b[^>]*>(.*?)</b>}im, "**\\1**")
|
|
77
|
-
content = content.gsub(%r{<em[^>]*>(.*?)</em>}im, "_\\1_")
|
|
78
|
-
content = content.gsub(%r{<i[^>]*>(.*?)</i>}im, "_\\1_")
|
|
79
|
-
content = content.gsub(%r{<code[^>]*>(.*?)</code>}im, "`\\1`")
|
|
80
|
-
content = content.gsub(%r{<a[^>]*href=["']([^"']*)["'][^>]*>(.*?)</a>}im, "[\\2](\\1)")
|
|
81
|
-
content = content.gsub(%r{<li[^>]*>(.*?)</li>}im, "- \\1\n")
|
|
82
|
-
|
|
83
|
-
# Remove remaining HTML tags
|
|
84
|
-
content = content.gsub(/<[^>]+>/, "")
|
|
85
|
-
|
|
86
|
-
# Decode HTML entities
|
|
87
|
-
content = content.gsub("<", "<")
|
|
88
|
-
content = content.gsub(">", ">")
|
|
89
|
-
content = content.gsub("&", "&")
|
|
90
|
-
content = content.gsub(""", "\"")
|
|
91
|
-
content = content.gsub("'", "'")
|
|
92
|
-
content = content.gsub(" ", " ")
|
|
93
|
-
|
|
94
|
-
# Clean up whitespace
|
|
95
|
-
content = content.gsub(/\n\n\n+/, "\n\n")
|
|
96
|
-
content.strip
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module Tools
|
|
5
|
-
module DocumentConverters
|
|
6
|
-
# Converts PDF documents to text with image extraction
|
|
7
|
-
class PdfConverter < BaseConverter
|
|
8
|
-
class << self
|
|
9
|
-
def gem_name
|
|
10
|
-
"pdf-reader"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def format_name
|
|
14
|
-
"PDF"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def extensions
|
|
18
|
-
[".pdf"]
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Convert a PDF document to text/content
|
|
23
|
-
# @param file_path [String] Path to the PDF file
|
|
24
|
-
# @return [String, RubyLLM::Content] Converted content or error message
|
|
25
|
-
def convert(file_path)
|
|
26
|
-
unless self.class.available?
|
|
27
|
-
return unsupported_format_reminder(self.class.format_name, self.class.gem_name)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
begin
|
|
31
|
-
require "pdf-reader"
|
|
32
|
-
require "tmpdir"
|
|
33
|
-
require "fileutils"
|
|
34
|
-
|
|
35
|
-
reader = PDF::Reader.new(file_path)
|
|
36
|
-
output = []
|
|
37
|
-
output << "PDF Document: #{File.basename(file_path)}"
|
|
38
|
-
output << "=" * 60
|
|
39
|
-
output << "Pages: #{reader.page_count}"
|
|
40
|
-
output << ""
|
|
41
|
-
|
|
42
|
-
# Extract images from the PDF
|
|
43
|
-
image_paths = ImageExtractors::PdfImageExtractor.extract_images(reader, file_path)
|
|
44
|
-
|
|
45
|
-
# Extract text from each page
|
|
46
|
-
reader.pages.each_with_index do |page, index|
|
|
47
|
-
output << "Page #{index + 1}:"
|
|
48
|
-
output << "-" * 60
|
|
49
|
-
text = page.text.strip
|
|
50
|
-
output << (text.empty? ? "(No text content on this page)" : text)
|
|
51
|
-
output << ""
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
text_content = output.join("\n")
|
|
55
|
-
|
|
56
|
-
# If there are images, return Content with attachments
|
|
57
|
-
if image_paths.any?
|
|
58
|
-
content = RubyLLM::Content.new(text_content)
|
|
59
|
-
image_paths.each do |image_path|
|
|
60
|
-
content.add_attachment(image_path)
|
|
61
|
-
end
|
|
62
|
-
content
|
|
63
|
-
else
|
|
64
|
-
# No images, return just text
|
|
65
|
-
text_content
|
|
66
|
-
end
|
|
67
|
-
rescue PDF::Reader::MalformedPDFError => e
|
|
68
|
-
error("PDF file is malformed: #{e.message}")
|
|
69
|
-
rescue PDF::Reader::UnsupportedFeatureError => e
|
|
70
|
-
error("PDF contains unsupported features: #{e.message}")
|
|
71
|
-
rescue StandardError => e
|
|
72
|
-
error("Failed to parse PDF file: #{e.message}")
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|