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,480 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module Agent
|
|
5
|
-
module ChatHelpers
|
|
6
|
-
# Integrates SwarmSDK's hook system with Agent::Chat
|
|
7
|
-
#
|
|
8
|
-
# Responsibilities:
|
|
9
|
-
# - Setup hook system (registry, executor, agent hooks)
|
|
10
|
-
# - Provide trigger methods for all hook events
|
|
11
|
-
# - Wrap ask() to inject user_prompt hooks
|
|
12
|
-
# - Handle hook results (halt, replace, continue, reprompt)
|
|
13
|
-
#
|
|
14
|
-
# This module is included in Agent::Chat and provides methods for triggering hooks.
|
|
15
|
-
# It overrides ask() to inject user_prompt hooks, but does NOT override
|
|
16
|
-
# handle_tool_calls (that's handled in Agent::Chat with explicit hook calls).
|
|
17
|
-
module HookIntegration
|
|
18
|
-
# Expose hook system components for ContextTracker
|
|
19
|
-
attr_reader :hook_executor, :hook_swarm, :hook_agent_hooks
|
|
20
|
-
|
|
21
|
-
# Setup the hook system for this agent chat
|
|
22
|
-
#
|
|
23
|
-
# This must be called after setup_context and before the first ask/complete.
|
|
24
|
-
# It wires up the hook system to trigger at the right times.
|
|
25
|
-
#
|
|
26
|
-
# @param registry [Hooks::Registry] Shared registry for named hooks and swarm defaults
|
|
27
|
-
# @param agent_definition [Agent::Definition] Agent configuration with hooks
|
|
28
|
-
# @param swarm [Swarm, nil] Reference to swarm for context
|
|
29
|
-
# @return [void]
|
|
30
|
-
def setup_hooks(registry:, agent_definition:, swarm: nil)
|
|
31
|
-
@hook_registry = registry
|
|
32
|
-
@hook_swarm = swarm
|
|
33
|
-
@hook_executor = Hooks::Executor.new(registry, logger: RubyLLM.logger)
|
|
34
|
-
|
|
35
|
-
# Extract agent hooks based on format
|
|
36
|
-
hooks = agent_definition.hooks || {}
|
|
37
|
-
|
|
38
|
-
# Check if hooks are pre-parsed HookDefinition objects (from DSL)
|
|
39
|
-
# or raw YAML hash (to be processed by Hooks::Adapter in pass_5)
|
|
40
|
-
@hook_agent_hooks = if hooks.is_a?(Hash) && hooks.values.all? { |v| v.is_a?(Array) && v.all? { |item| item.is_a?(Hooks::Definition) } }
|
|
41
|
-
# DSL hooks - already parsed, use them
|
|
42
|
-
hooks
|
|
43
|
-
else
|
|
44
|
-
# YAML hooks - raw hash, will be processed in pass_5 by Hooks::Adapter
|
|
45
|
-
# For now, use empty hash (pass_5 will add them later)
|
|
46
|
-
{}
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Add a hook programmatically at runtime
|
|
51
|
-
#
|
|
52
|
-
# This allows agents to add hooks dynamically, which is useful for
|
|
53
|
-
# implementing adaptive behavior or runtime monitoring.
|
|
54
|
-
#
|
|
55
|
-
# @param event [Symbol] Event type (e.g., :pre_tool_use)
|
|
56
|
-
# @param matcher [String, Regexp, nil] Optional regex pattern for tool names
|
|
57
|
-
# @param priority [Integer] Execution priority (higher = earlier)
|
|
58
|
-
# @param block [Proc] Hook implementation
|
|
59
|
-
def add_hook(event, matcher: nil, priority: 0, &block)
|
|
60
|
-
raise ArgumentError, "Hooks not set up. Call setup_hooks first." unless @hook_executor
|
|
61
|
-
|
|
62
|
-
definition = Hooks::Definition.new(
|
|
63
|
-
event: event,
|
|
64
|
-
matcher: matcher,
|
|
65
|
-
priority: priority,
|
|
66
|
-
proc: block,
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
@hook_agent_hooks[event] ||= []
|
|
70
|
-
@hook_agent_hooks[event] << definition
|
|
71
|
-
@hook_agent_hooks[event].sort_by! { |cb| -cb.priority }
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# NOTE: The ask() method override has been removed.
|
|
75
|
-
#
|
|
76
|
-
# In the new wrapper-based architecture, Agent::Chat#ask handles:
|
|
77
|
-
# 1. System reminder injection
|
|
78
|
-
# 2. User prompt hooks via trigger_user_prompt
|
|
79
|
-
# 3. Global semaphore acquisition
|
|
80
|
-
# 4. Delegation to RubyLLM::Chat
|
|
81
|
-
#
|
|
82
|
-
# The hook integration is now done directly in Agent::Chat#ask rather than
|
|
83
|
-
# through module override, since there's no inheritance chain to call super on.
|
|
84
|
-
|
|
85
|
-
# Override check_context_warnings to trigger our hook system
|
|
86
|
-
#
|
|
87
|
-
# This wraps the default context warning behavior to also trigger hooks.
|
|
88
|
-
# Unified implementation that:
|
|
89
|
-
# 1. Emits context_threshold_hit events (for snapshot reconstruction)
|
|
90
|
-
# 2. Optionally triggers automatic compression at 60% (if no custom handler)
|
|
91
|
-
# 3. Emits context_limit_warning events (backward compatibility)
|
|
92
|
-
# 4. Triggers user-defined context_warning hooks
|
|
93
|
-
def check_context_warnings
|
|
94
|
-
return unless respond_to?(:context_usage_percentage)
|
|
95
|
-
|
|
96
|
-
current_percentage = context_usage_percentage
|
|
97
|
-
|
|
98
|
-
Context::CONTEXT_WARNING_THRESHOLDS.each do |threshold|
|
|
99
|
-
# Only warn once per threshold
|
|
100
|
-
next if @agent_context.warning_threshold_hit?(threshold)
|
|
101
|
-
next if current_percentage < threshold
|
|
102
|
-
|
|
103
|
-
# Mark threshold as hit
|
|
104
|
-
@agent_context.hit_warning_threshold?(threshold)
|
|
105
|
-
|
|
106
|
-
# Emit context_threshold_hit event (for snapshot reconstruction) - CRITICAL
|
|
107
|
-
LogStream.emit(
|
|
108
|
-
type: "context_threshold_hit",
|
|
109
|
-
agent: @agent_context.name,
|
|
110
|
-
threshold: threshold,
|
|
111
|
-
current_usage_percentage: current_percentage.round(2),
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# Check if user has defined custom handler for context_warning
|
|
115
|
-
# Custom handlers take responsibility for managing context at this threshold
|
|
116
|
-
has_custom_handler = (@hook_agent_hooks[:context_warning] || []).any?
|
|
117
|
-
|
|
118
|
-
# Trigger automatic compression at 60% ONLY if no custom handler
|
|
119
|
-
compression_triggered = false
|
|
120
|
-
if threshold == Context::COMPRESSION_THRESHOLD && !has_custom_handler
|
|
121
|
-
compressed_count = apply_automatic_compression
|
|
122
|
-
compression_triggered = compressed_count > 0
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Emit legacy context_limit_warning for backwards compatibility
|
|
126
|
-
LogStream.emit(
|
|
127
|
-
type: "context_limit_warning",
|
|
128
|
-
agent: @agent_context.name,
|
|
129
|
-
model: model_id,
|
|
130
|
-
threshold: "#{threshold}%",
|
|
131
|
-
current_usage: "#{current_percentage}%",
|
|
132
|
-
tokens_used: cumulative_total_tokens,
|
|
133
|
-
tokens_remaining: tokens_remaining,
|
|
134
|
-
context_limit: context_limit,
|
|
135
|
-
metadata: @agent_context.metadata,
|
|
136
|
-
compression_triggered: compression_triggered,
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
# Trigger hook system (user-defined handlers)
|
|
140
|
-
trigger_context_warning(threshold, current_percentage) if @hook_executor
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Trigger pre_tool_use hooks
|
|
145
|
-
#
|
|
146
|
-
# Should be called by Agent::Chat before tool execution.
|
|
147
|
-
# Returns a hash indicating whether to proceed and any custom result.
|
|
148
|
-
#
|
|
149
|
-
# @param tool_call [RubyLLM::ToolCall] Tool call from LLM
|
|
150
|
-
# @return [Hash] { proceed: true/false, custom_result: result (if any) }
|
|
151
|
-
def trigger_pre_tool_use(tool_call)
|
|
152
|
-
return { proceed: true } unless @hook_executor
|
|
153
|
-
|
|
154
|
-
context = build_hook_context(
|
|
155
|
-
event: :pre_tool_use,
|
|
156
|
-
tool_call: wrap_tool_call_to_hooks(tool_call),
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
agent_hooks = @hook_agent_hooks[:pre_tool_use] || []
|
|
160
|
-
|
|
161
|
-
result = @hook_executor.execute_safe(
|
|
162
|
-
event: :pre_tool_use,
|
|
163
|
-
context: context,
|
|
164
|
-
callbacks: agent_hooks,
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
# Return custom result if hook provides one
|
|
168
|
-
if result.replace?
|
|
169
|
-
{ proceed: false, custom_result: result.value }
|
|
170
|
-
elsif result.halt?
|
|
171
|
-
{ proceed: false, custom_result: result.value || "Tool execution blocked by hook" }
|
|
172
|
-
elsif result.finish_agent?
|
|
173
|
-
# Finish agent execution immediately with this message
|
|
174
|
-
{ proceed: false, finish_agent: true, custom_result: result.value }
|
|
175
|
-
elsif result.finish_swarm?
|
|
176
|
-
# Finish entire swarm execution immediately with this message
|
|
177
|
-
{ proceed: false, finish_swarm: true, custom_result: result.value }
|
|
178
|
-
else
|
|
179
|
-
{ proceed: true }
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Trigger post_tool_use hooks
|
|
184
|
-
#
|
|
185
|
-
# Should be called by Agent::Chat after tool execution.
|
|
186
|
-
# Returns modified result if hook replaces it, or a special marker for finish actions.
|
|
187
|
-
#
|
|
188
|
-
# @param result [String, Object] Tool execution result
|
|
189
|
-
# @param tool_call [RubyLLM::ToolCall] Tool call object with full context
|
|
190
|
-
# @return [Object, Hash] Modified result if hook replaces it, hash with :finish_agent or :finish_swarm if finishing, otherwise original result
|
|
191
|
-
def trigger_post_tool_use(result, tool_call:)
|
|
192
|
-
return result unless @hook_executor
|
|
193
|
-
|
|
194
|
-
# Extract tracking digest for Read/MemoryRead tools
|
|
195
|
-
metadata_with_digest = extract_tool_tracking_digest(tool_call, result)
|
|
196
|
-
|
|
197
|
-
context = build_hook_context(
|
|
198
|
-
event: :post_tool_use,
|
|
199
|
-
tool_result: wrap_tool_result(tool_call.id, tool_call.name, result),
|
|
200
|
-
metadata: metadata_with_digest,
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
agent_hooks = @hook_agent_hooks[:post_tool_use] || []
|
|
204
|
-
|
|
205
|
-
hook_result = @hook_executor.execute_safe(
|
|
206
|
-
event: :post_tool_use,
|
|
207
|
-
context: context,
|
|
208
|
-
callbacks: agent_hooks,
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
# Return modified result or finish markers
|
|
212
|
-
if hook_result.replace?
|
|
213
|
-
hook_result.value
|
|
214
|
-
elsif hook_result.finish_agent?
|
|
215
|
-
{ __finish_agent__: true, message: hook_result.value }
|
|
216
|
-
elsif hook_result.finish_swarm?
|
|
217
|
-
{ __finish_swarm__: true, message: hook_result.value }
|
|
218
|
-
else
|
|
219
|
-
result
|
|
220
|
-
end
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
private
|
|
224
|
-
|
|
225
|
-
# Apply automatic message compression when context threshold is hit
|
|
226
|
-
#
|
|
227
|
-
# Called when context usage crosses 60% threshold and no custom handler exists.
|
|
228
|
-
# Compresses old tool results to save context window space while preserving accuracy.
|
|
229
|
-
#
|
|
230
|
-
# @return [Integer] Number of messages compressed (0 if compression not applied)
|
|
231
|
-
def apply_automatic_compression
|
|
232
|
-
return 0 unless respond_to?(:context_manager) && respond_to?(:messages)
|
|
233
|
-
|
|
234
|
-
# Calculate tokens before compression
|
|
235
|
-
tokens_before = cumulative_total_tokens
|
|
236
|
-
|
|
237
|
-
# Get compressed messages from ContextManager
|
|
238
|
-
compressed = context_manager.auto_compress_on_threshold(messages, keep_recent: 10)
|
|
239
|
-
|
|
240
|
-
# Count how many messages were actually compressed
|
|
241
|
-
messages_compressed = compressed.count do |msg|
|
|
242
|
-
msg.content.to_s.include?("[truncated for context management]")
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# Replace messages using proper abstraction
|
|
246
|
-
replace_messages(compressed)
|
|
247
|
-
|
|
248
|
-
# Log compression event
|
|
249
|
-
LogStream.emit(
|
|
250
|
-
type: "context_compression",
|
|
251
|
-
agent: @agent_context.name,
|
|
252
|
-
total_messages: message_count,
|
|
253
|
-
messages_compressed: messages_compressed,
|
|
254
|
-
tokens_before: tokens_before,
|
|
255
|
-
current_usage: "#{context_usage_percentage}%",
|
|
256
|
-
compression_strategy: "progressive_tool_result_compression",
|
|
257
|
-
keep_recent: 10,
|
|
258
|
-
triggered_by: "auto_compression_threshold",
|
|
259
|
-
) if LogStream.enabled?
|
|
260
|
-
|
|
261
|
-
messages_compressed
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
# Trigger context_warning hooks
|
|
265
|
-
#
|
|
266
|
-
# Hooks have access to the chat instance via metadata[:chat]
|
|
267
|
-
# to access and manipulate the messages array.
|
|
268
|
-
#
|
|
269
|
-
# @param threshold [Integer] Warning threshold percentage
|
|
270
|
-
# @param current_usage [Float] Current usage percentage
|
|
271
|
-
# @return [void]
|
|
272
|
-
def trigger_context_warning(threshold, current_usage)
|
|
273
|
-
return unless @hook_executor
|
|
274
|
-
|
|
275
|
-
context = build_hook_context(
|
|
276
|
-
event: :context_warning,
|
|
277
|
-
metadata: {
|
|
278
|
-
chat: self, # Provide access to chat instance (for messages array)
|
|
279
|
-
threshold: threshold,
|
|
280
|
-
percentage: current_usage,
|
|
281
|
-
tokens_used: cumulative_total_tokens,
|
|
282
|
-
tokens_remaining: tokens_remaining,
|
|
283
|
-
context_limit: context_limit,
|
|
284
|
-
},
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
agent_hooks = @hook_agent_hooks[:context_warning] || []
|
|
288
|
-
|
|
289
|
-
@hook_executor.execute_safe(
|
|
290
|
-
event: :context_warning,
|
|
291
|
-
context: context,
|
|
292
|
-
callbacks: agent_hooks,
|
|
293
|
-
)
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
# Trigger user_prompt hooks
|
|
297
|
-
#
|
|
298
|
-
# This fires before sending a user message to the LLM.
|
|
299
|
-
# Can halt execution or append hook stdout to prompt.
|
|
300
|
-
#
|
|
301
|
-
# @param prompt [String] User's message/prompt
|
|
302
|
-
# @param source [String] Source of the prompt ("user" or "delegation")
|
|
303
|
-
# @return [Hash] { halted: bool, halt_message: String, modified_prompt: String }
|
|
304
|
-
def trigger_user_prompt(prompt, source: "user")
|
|
305
|
-
return { halted: false, modified_prompt: prompt } unless @hook_executor
|
|
306
|
-
|
|
307
|
-
# Get tool names without delegation tools using proper abstraction
|
|
308
|
-
actual_tools = if respond_to?(:non_delegation_tool_names) && @agent_context
|
|
309
|
-
non_delegation_tool_names
|
|
310
|
-
else
|
|
311
|
-
[]
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
# Extract agent names from delegation tool names
|
|
315
|
-
delegate_agents = if @agent_context&.delegation_tools
|
|
316
|
-
@agent_context.delegation_tools.map { |tool_name| @context_tracker.extract_delegate_agent_name(tool_name) }
|
|
317
|
-
else
|
|
318
|
-
[]
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
context = build_hook_context(
|
|
322
|
-
event: :user_prompt,
|
|
323
|
-
metadata: {
|
|
324
|
-
prompt: prompt,
|
|
325
|
-
message_count: message_count,
|
|
326
|
-
model: model_id,
|
|
327
|
-
provider: model_provider,
|
|
328
|
-
tools: actual_tools,
|
|
329
|
-
delegates_to: delegate_agents,
|
|
330
|
-
source: source,
|
|
331
|
-
timestamp: Time.now.utc.iso8601,
|
|
332
|
-
},
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
agent_hooks = @hook_agent_hooks[:user_prompt] || []
|
|
336
|
-
|
|
337
|
-
result = @hook_executor.execute_safe(
|
|
338
|
-
event: :user_prompt,
|
|
339
|
-
context: context,
|
|
340
|
-
callbacks: agent_hooks,
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
# Handle hook result
|
|
344
|
-
if result.halt?
|
|
345
|
-
# Hook blocked execution
|
|
346
|
-
{ halted: true, halt_message: result.value }
|
|
347
|
-
elsif result.replace?
|
|
348
|
-
# Hook provided stdout to append to prompt (exit code 0)
|
|
349
|
-
modified_prompt = "#{prompt}\n\n<hook-context>\n#{result.value}\n</hook-context>"
|
|
350
|
-
{ halted: false, modified_prompt: modified_prompt }
|
|
351
|
-
else
|
|
352
|
-
# Normal continue
|
|
353
|
-
{ halted: false, modified_prompt: prompt }
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
# Build a hook context object
|
|
358
|
-
#
|
|
359
|
-
# @param event [Symbol] Event type
|
|
360
|
-
# @param tool_call [Hooks::ToolCall, nil] Tool call object
|
|
361
|
-
# @param tool_result [Hooks::ToolResult, nil] Tool result object
|
|
362
|
-
# @param metadata [Hash] Additional metadata
|
|
363
|
-
# @return [Hooks::Context] Context object
|
|
364
|
-
def build_hook_context(event:, tool_call: nil, tool_result: nil, metadata: {})
|
|
365
|
-
Hooks::Context.new(
|
|
366
|
-
event: event,
|
|
367
|
-
agent_name: @agent_context&.name || "unknown",
|
|
368
|
-
agent_definition: nil, # Could store this in setup_hooks if needed
|
|
369
|
-
swarm: @hook_swarm,
|
|
370
|
-
tool_call: tool_call,
|
|
371
|
-
tool_result: tool_result,
|
|
372
|
-
metadata: metadata,
|
|
373
|
-
)
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
# Wrap a RubyLLM tool call in our Hooks::ToolCall value object
|
|
377
|
-
#
|
|
378
|
-
# @param tool_call [RubyLLM::ToolCall] RubyLLM tool call
|
|
379
|
-
# @return [Hooks::ToolCall] Our wrapped tool call
|
|
380
|
-
def wrap_tool_call_to_hooks(tool_call)
|
|
381
|
-
Hooks::ToolCall.new(
|
|
382
|
-
id: tool_call.id,
|
|
383
|
-
name: tool_call.name,
|
|
384
|
-
parameters: tool_call.arguments,
|
|
385
|
-
)
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
# Extract tracking digest for Read/MemoryRead tools
|
|
389
|
-
#
|
|
390
|
-
# Queries the appropriate tracker after tool execution to get the digest
|
|
391
|
-
# that was calculated and stored during the read operation.
|
|
392
|
-
#
|
|
393
|
-
# @param tool_call [RubyLLM::ToolCall] Tool call with arguments
|
|
394
|
-
# @param result [Object] Tool execution result (to check for errors)
|
|
395
|
-
# @return [Hash] Metadata hash with digest if applicable
|
|
396
|
-
def extract_tool_tracking_digest(tool_call, result)
|
|
397
|
-
# Only add digest for successful Read/MemoryRead tool calls
|
|
398
|
-
return {} if result.is_a?(StandardError)
|
|
399
|
-
return {} unless ["Read", "MemoryRead"].include?(tool_call.name)
|
|
400
|
-
|
|
401
|
-
# Extract path from arguments
|
|
402
|
-
path = case tool_call.name
|
|
403
|
-
when "Read"
|
|
404
|
-
tool_call.arguments[:file_path] || tool_call.arguments["file_path"]
|
|
405
|
-
when "MemoryRead"
|
|
406
|
-
tool_call.arguments[:file_path] || tool_call.arguments["file_path"]
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
return {} unless path
|
|
410
|
-
|
|
411
|
-
# Query tracker for digest
|
|
412
|
-
digest = case tool_call.name
|
|
413
|
-
when "Read"
|
|
414
|
-
Tools::Stores::ReadTracker.get_read_files(@agent_context.name)[File.expand_path(path)]
|
|
415
|
-
else
|
|
416
|
-
# Query registered plugins for digest (e.g., MemoryRead from SwarmMemory plugin)
|
|
417
|
-
query_plugin_for_digest(tool_call.name, path)
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
digest ? { read_digest: digest, read_path: path } : {}
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
# Query registered plugins for a tool result digest
|
|
424
|
-
#
|
|
425
|
-
# This allows plugins to provide digest tracking for their own tools
|
|
426
|
-
# (e.g., MemoryRead tracking in SwarmMemory plugin).
|
|
427
|
-
#
|
|
428
|
-
# @param tool_name [String] Name of the tool
|
|
429
|
-
# @param path [String] Path or identifier of the resource
|
|
430
|
-
# @return [String, nil] Digest from first plugin that responds, or nil
|
|
431
|
-
def query_plugin_for_digest(tool_name, path)
|
|
432
|
-
return unless @agent_context
|
|
433
|
-
|
|
434
|
-
PluginRegistry.all.each do |plugin|
|
|
435
|
-
digest = plugin.get_tool_result_digest(
|
|
436
|
-
agent_name: @agent_context.name,
|
|
437
|
-
tool_name: tool_name,
|
|
438
|
-
path: path,
|
|
439
|
-
)
|
|
440
|
-
return digest if digest
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
nil
|
|
444
|
-
end
|
|
445
|
-
|
|
446
|
-
# Wrap a tool result in our Hooks::ToolResult value object
|
|
447
|
-
#
|
|
448
|
-
# @param tool_call_id [String] Tool call ID
|
|
449
|
-
# @param tool_name [String] Tool name
|
|
450
|
-
# @param result [Object] Tool execution result
|
|
451
|
-
# @return [Hooks::ToolResult] Our wrapped result
|
|
452
|
-
def wrap_tool_result(tool_call_id, tool_name, result)
|
|
453
|
-
success = !result.is_a?(StandardError)
|
|
454
|
-
error = result.is_a?(StandardError) ? result.message : nil
|
|
455
|
-
|
|
456
|
-
Hooks::ToolResult.new(
|
|
457
|
-
tool_call_id: tool_call_id,
|
|
458
|
-
tool_name: tool_name,
|
|
459
|
-
content: success ? result : nil,
|
|
460
|
-
success: success,
|
|
461
|
-
error: error,
|
|
462
|
-
)
|
|
463
|
-
end
|
|
464
|
-
|
|
465
|
-
# Check if a tool call is a delegation tool
|
|
466
|
-
#
|
|
467
|
-
# Delegation tools fire their own pre_delegation/post_delegation events
|
|
468
|
-
# and should NOT fire pre_tool_use/post_tool_use events.
|
|
469
|
-
#
|
|
470
|
-
# @param tool_call [RubyLLM::ToolCall] Tool call to check
|
|
471
|
-
# @return [Boolean] true if this is a delegation tool
|
|
472
|
-
def delegation_tool_call?(tool_call)
|
|
473
|
-
return false unless @agent_context
|
|
474
|
-
|
|
475
|
-
@agent_context.delegation_tool?(tool_call.name)
|
|
476
|
-
end
|
|
477
|
-
end
|
|
478
|
-
end
|
|
479
|
-
end
|
|
480
|
-
end
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
module Agent
|
|
5
|
-
module ChatHelpers
|
|
6
|
-
# LLM instrumentation for API request/response logging
|
|
7
|
-
#
|
|
8
|
-
# Extracted from Chat to reduce class size and centralize observability logic.
|
|
9
|
-
module Instrumentation
|
|
10
|
-
private
|
|
11
|
-
|
|
12
|
-
# Inject LLM instrumentation middleware for API request/response logging
|
|
13
|
-
#
|
|
14
|
-
# @return [void]
|
|
15
|
-
def inject_llm_instrumentation
|
|
16
|
-
return unless @provider
|
|
17
|
-
|
|
18
|
-
faraday_conn = @provider.connection&.connection
|
|
19
|
-
return unless faraday_conn
|
|
20
|
-
return if @llm_instrumentation_injected
|
|
21
|
-
|
|
22
|
-
provider_name = @provider.class.name.split("::").last.downcase
|
|
23
|
-
|
|
24
|
-
faraday_conn.builder.insert(
|
|
25
|
-
0,
|
|
26
|
-
SwarmSDK::Agent::LLMInstrumentationMiddleware,
|
|
27
|
-
on_request: method(:handle_llm_api_request),
|
|
28
|
-
on_response: method(:handle_llm_api_response),
|
|
29
|
-
provider_name: provider_name,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
@llm_instrumentation_injected = true
|
|
33
|
-
|
|
34
|
-
RubyLLM.logger.debug("SwarmSDK: Injected LLM instrumentation middleware for agent #{@agent_name}")
|
|
35
|
-
rescue StandardError => e
|
|
36
|
-
LogStream.emit_error(e, source: "instrumentation", context: "inject_middleware", agent: @agent_name)
|
|
37
|
-
RubyLLM.logger.debug("SwarmSDK: Failed to inject LLM instrumentation: #{e.message}")
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Handle LLM API request event
|
|
41
|
-
#
|
|
42
|
-
# @param data [Hash] Request data from middleware
|
|
43
|
-
def handle_llm_api_request(data)
|
|
44
|
-
return unless LogStream.emitter
|
|
45
|
-
|
|
46
|
-
LogStream.emit(
|
|
47
|
-
type: "llm_api_request",
|
|
48
|
-
agent: @agent_name,
|
|
49
|
-
swarm_id: @agent_context&.swarm_id,
|
|
50
|
-
parent_swarm_id: @agent_context&.parent_swarm_id,
|
|
51
|
-
**data,
|
|
52
|
-
)
|
|
53
|
-
rescue StandardError => e
|
|
54
|
-
LogStream.emit_error(e, source: "instrumentation", context: "emit_llm_api_request", agent: @agent_name)
|
|
55
|
-
RubyLLM.logger.debug("SwarmSDK: Error emitting llm_api_request event: #{e.message}")
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Handle LLM API response event
|
|
59
|
-
#
|
|
60
|
-
# @param data [Hash] Response data from middleware
|
|
61
|
-
def handle_llm_api_response(data)
|
|
62
|
-
return unless LogStream.emitter
|
|
63
|
-
|
|
64
|
-
LogStream.emit(
|
|
65
|
-
type: "llm_api_response",
|
|
66
|
-
agent: @agent_name,
|
|
67
|
-
swarm_id: @agent_context&.swarm_id,
|
|
68
|
-
parent_swarm_id: @agent_context&.parent_swarm_id,
|
|
69
|
-
**data,
|
|
70
|
-
)
|
|
71
|
-
rescue StandardError => e
|
|
72
|
-
LogStream.emit_error(e, source: "instrumentation", context: "emit_llm_api_response", agent: @agent_name)
|
|
73
|
-
RubyLLM.logger.debug("SwarmSDK: Error emitting llm_api_response event: #{e.message}")
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|