swarm_memory 2.1.4 → 2.1.6

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