swarm_sdk 2.2.0 → 2.4.0
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_sdk/agent/builder.rb +58 -0
- data/lib/swarm_sdk/agent/chat.rb +527 -1059
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +262 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +11 -13
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
- data/lib/swarm_sdk/agent/context.rb +1 -2
- data/lib/swarm_sdk/agent/definition.rb +66 -154
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
- data/lib/swarm_sdk/builders/base_builder.rb +409 -0
- data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
- data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
- data/lib/swarm_sdk/concerns/validatable.rb +55 -0
- data/lib/swarm_sdk/config.rb +301 -0
- data/lib/swarm_sdk/configuration/parser.rb +353 -0
- data/lib/swarm_sdk/configuration/translator.rb +255 -0
- data/lib/swarm_sdk/configuration.rb +65 -543
- data/lib/swarm_sdk/context_compactor/token_counter.rb +2 -6
- data/lib/swarm_sdk/context_compactor.rb +6 -11
- data/lib/swarm_sdk/context_management/builder.rb +128 -0
- data/lib/swarm_sdk/context_management/context.rb +328 -0
- data/lib/swarm_sdk/defaults.rb +196 -0
- data/lib/swarm_sdk/events_to_messages.rb +18 -0
- data/lib/swarm_sdk/hooks/adapter.rb +3 -3
- data/lib/swarm_sdk/hooks/shell_executor.rb +4 -2
- data/lib/swarm_sdk/log_collector.rb +179 -29
- data/lib/swarm_sdk/log_stream.rb +29 -0
- data/lib/swarm_sdk/models.json +4333 -1
- data/lib/swarm_sdk/node_context.rb +1 -1
- data/lib/swarm_sdk/observer/builder.rb +81 -0
- data/lib/swarm_sdk/observer/config.rb +45 -0
- data/lib/swarm_sdk/observer/manager.rb +236 -0
- data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
- data/lib/swarm_sdk/plugin.rb +93 -3
- data/lib/swarm_sdk/snapshot.rb +6 -6
- data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
- data/lib/swarm_sdk/state_restorer.rb +136 -151
- data/lib/swarm_sdk/state_snapshot.rb +65 -100
- data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
- data/lib/swarm_sdk/swarm/builder.rb +44 -578
- data/lib/swarm_sdk/swarm/executor.rb +213 -0
- data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
- data/lib/swarm_sdk/swarm/tool_configurator.rb +44 -140
- data/lib/swarm_sdk/swarm.rb +146 -689
- data/lib/swarm_sdk/tools/bash.rb +14 -8
- data/lib/swarm_sdk/tools/delegate.rb +61 -43
- data/lib/swarm_sdk/tools/edit.rb +8 -13
- data/lib/swarm_sdk/tools/glob.rb +12 -4
- data/lib/swarm_sdk/tools/grep.rb +7 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
- data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
- data/lib/swarm_sdk/tools/read.rb +16 -18
- data/lib/swarm_sdk/tools/registry.rb +122 -10
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +9 -5
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
- data/lib/swarm_sdk/tools/todo_write.rb +7 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +20 -17
- data/lib/swarm_sdk/tools/write.rb +8 -13
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
- data/lib/swarm_sdk/workflow/builder.rb +143 -0
- data/lib/swarm_sdk/workflow/executor.rb +497 -0
- data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +7 -5
- data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +5 -3
- data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
- data/lib/swarm_sdk.rb +64 -104
- metadata +68 -15
- data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
|
@@ -44,6 +44,7 @@ module SwarmSDK
|
|
|
44
44
|
# - `agent_step`: Reconstructs assistant message with tool calls
|
|
45
45
|
# - `agent_stop`: Reconstructs final assistant message
|
|
46
46
|
# - `tool_result`: Reconstructs tool result message
|
|
47
|
+
# - `delegation_result`: Reconstructs tool result message from delegation
|
|
47
48
|
class EventsToMessages
|
|
48
49
|
class << self
|
|
49
50
|
# Reconstruct messages for an agent from event stream
|
|
@@ -90,6 +91,8 @@ module SwarmSDK
|
|
|
90
91
|
reconstruct_assistant_message(event)
|
|
91
92
|
when "tool_result"
|
|
92
93
|
reconstruct_tool_result_message(event)
|
|
94
|
+
when "delegation_result"
|
|
95
|
+
reconstruct_delegation_result_message(event)
|
|
93
96
|
end
|
|
94
97
|
|
|
95
98
|
messages << message if message
|
|
@@ -158,6 +161,21 @@ module SwarmSDK
|
|
|
158
161
|
)
|
|
159
162
|
end
|
|
160
163
|
|
|
164
|
+
# Reconstruct tool result message from delegation_result event
|
|
165
|
+
#
|
|
166
|
+
# delegation_result events are emitted when a delegation completes,
|
|
167
|
+
# and they should be converted to tool result messages in the conversation.
|
|
168
|
+
#
|
|
169
|
+
# @param event [Hash] delegation_result event
|
|
170
|
+
# @return [RubyLLM::Message] Tool result message
|
|
171
|
+
def reconstruct_delegation_result_message(event)
|
|
172
|
+
RubyLLM::Message.new(
|
|
173
|
+
role: :tool,
|
|
174
|
+
content: event[:result].to_s,
|
|
175
|
+
tool_call_id: event[:tool_call_id],
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
161
179
|
# Parse timestamp string to Time object
|
|
162
180
|
#
|
|
163
181
|
# @param timestamp [String, nil] ISO 8601 timestamp
|
|
@@ -167,7 +167,7 @@ module SwarmSDK
|
|
|
167
167
|
def create_hook_callback(hook_def, event_symbol, agent_name, swarm_name)
|
|
168
168
|
# Support both string and symbol keys (YAML may be symbolized)
|
|
169
169
|
command = hook_def[:command] || hook_def["command"]
|
|
170
|
-
timeout = hook_def[:timeout] || hook_def["timeout"] ||
|
|
170
|
+
timeout = hook_def[:timeout] || hook_def["timeout"] || SwarmSDK.config.hook_shell_timeout
|
|
171
171
|
|
|
172
172
|
lambda do |context|
|
|
173
173
|
input_json = build_input_json(context, event_symbol, agent_name)
|
|
@@ -191,7 +191,7 @@ module SwarmSDK
|
|
|
191
191
|
def create_all_agents_hook_callback(hook_def, event_symbol, swarm_name)
|
|
192
192
|
# Support both string and symbol keys (YAML may be symbolized)
|
|
193
193
|
command = hook_def[:command] || hook_def["command"]
|
|
194
|
-
timeout = hook_def[:timeout] || hook_def["timeout"] ||
|
|
194
|
+
timeout = hook_def[:timeout] || hook_def["timeout"] || SwarmSDK.config.hook_shell_timeout
|
|
195
195
|
|
|
196
196
|
lambda do |context|
|
|
197
197
|
# Agent name comes from context
|
|
@@ -217,7 +217,7 @@ module SwarmSDK
|
|
|
217
217
|
def create_swarm_hook_callback(hook_def, event_symbol, swarm_name)
|
|
218
218
|
# Support both string and symbol keys (YAML may be symbolized)
|
|
219
219
|
command = hook_def[:command] || hook_def["command"]
|
|
220
|
-
timeout = hook_def[:timeout] || hook_def["timeout"] ||
|
|
220
|
+
timeout = hook_def[:timeout] || hook_def["timeout"] || SwarmSDK.config.hook_shell_timeout
|
|
221
221
|
|
|
222
222
|
lambda do |context|
|
|
223
223
|
input_json = build_swarm_input_json(context, event_symbol, swarm_name)
|
|
@@ -47,7 +47,7 @@ module SwarmSDK
|
|
|
47
47
|
# )
|
|
48
48
|
# # => Result (continue or halt based on exit code)
|
|
49
49
|
class ShellExecutor
|
|
50
|
-
|
|
50
|
+
# NOTE: Timeout now accessed via SwarmSDK.config.hook_shell_timeout
|
|
51
51
|
|
|
52
52
|
class << self
|
|
53
53
|
# Execute a shell command hook
|
|
@@ -59,7 +59,9 @@ module SwarmSDK
|
|
|
59
59
|
# @param swarm_name [String, nil] Swarm name for environment variables
|
|
60
60
|
# @param event [Symbol] Event type for context-aware behavior
|
|
61
61
|
# @return [Result] Result based on exit code (continue or halt)
|
|
62
|
-
def execute(command:, input_json:, timeout:
|
|
62
|
+
def execute(command:, input_json:, timeout: nil, agent_name: nil, swarm_name: nil, event: nil)
|
|
63
|
+
timeout ||= SwarmSDK.config.hook_shell_timeout
|
|
64
|
+
|
|
63
65
|
# Build environment variables
|
|
64
66
|
env = build_environment(agent_name: agent_name, swarm_name: swarm_name)
|
|
65
67
|
|
|
@@ -1,76 +1,226 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SwarmSDK
|
|
4
|
-
# LogCollector manages subscriber callbacks for log events.
|
|
4
|
+
# LogCollector manages subscriber callbacks for log events with filtering support.
|
|
5
5
|
#
|
|
6
6
|
# This module acts as an emitter implementation that forwards events
|
|
7
7
|
# to user-registered callbacks. It's designed to be set as the LogStream
|
|
8
8
|
# emitter during swarm execution.
|
|
9
9
|
#
|
|
10
|
+
# ## Features
|
|
11
|
+
#
|
|
12
|
+
# - **Filtered Subscriptions**: Subscribe to specific agents, event types, or swarm IDs
|
|
13
|
+
# - **Unsubscribe Support**: Remove subscriptions by ID to prevent memory leaks
|
|
14
|
+
# - **Error Isolation**: One subscriber's error doesn't break others
|
|
15
|
+
# - **Thread Safety**: Fiber-local storage for multi-threaded environments
|
|
16
|
+
#
|
|
10
17
|
# ## Thread Safety for Multi-Threaded Environments (Puma, Sidekiq)
|
|
11
18
|
#
|
|
12
|
-
#
|
|
19
|
+
# Subscriptions are stored in Fiber-local storage (Fiber[:log_subscriptions]) instead
|
|
13
20
|
# of class instance variables. This ensures callbacks registered in the parent
|
|
14
21
|
# thread/fiber are accessible to child fibers created by Async reactor.
|
|
15
22
|
#
|
|
16
|
-
# Why: In Puma/Sidekiq, class instance variables (@
|
|
23
|
+
# Why: In Puma/Sidekiq, class instance variables (@subscriptions) are thread-isolated
|
|
17
24
|
# and don't properly propagate to child fibers. Using Fiber-local storage ensures
|
|
18
|
-
# events emitted from within Async blocks can reach registered
|
|
25
|
+
# events emitted from within Async blocks can reach registered subscriptions.
|
|
19
26
|
#
|
|
20
27
|
# Child fibers inherit parent fiber-local storage automatically, so events
|
|
21
28
|
# emitted from agent callbacks (on_tool_call, on_end_message, etc.) executing
|
|
22
|
-
# in child fibers can still reach the parent's registered
|
|
29
|
+
# in child fibers can still reach the parent's registered subscriptions.
|
|
23
30
|
#
|
|
24
31
|
# ## Usage
|
|
25
32
|
#
|
|
26
|
-
# #
|
|
27
|
-
# LogCollector.
|
|
28
|
-
#
|
|
29
|
-
#
|
|
33
|
+
# # Subscribe to all events
|
|
34
|
+
# sub_id = LogCollector.subscribe { |event| puts event }
|
|
35
|
+
#
|
|
36
|
+
# # Subscribe to specific agent
|
|
37
|
+
# sub_id = LogCollector.subscribe(filter: { agent: :backend }) { |event|
|
|
38
|
+
# puts "Backend: #{event}"
|
|
39
|
+
# }
|
|
30
40
|
#
|
|
31
|
-
# #
|
|
32
|
-
# LogCollector.
|
|
41
|
+
# # Subscribe to specific event types
|
|
42
|
+
# sub_id = LogCollector.subscribe(filter: { type: ["tool_call", "tool_result"] }) { |event|
|
|
43
|
+
# log_tool_activity(event)
|
|
44
|
+
# }
|
|
45
|
+
#
|
|
46
|
+
# # Subscribe with regex matching
|
|
47
|
+
# sub_id = LogCollector.subscribe(filter: { type: /^tool_/ }) { |event|
|
|
48
|
+
# track_tool_usage(event)
|
|
49
|
+
# }
|
|
50
|
+
#
|
|
51
|
+
# # Unsubscribe when done
|
|
52
|
+
# LogCollector.unsubscribe(sub_id)
|
|
33
53
|
#
|
|
34
54
|
# # After execution, reset for next use
|
|
35
55
|
# LogCollector.reset!
|
|
36
56
|
#
|
|
37
57
|
module LogCollector
|
|
58
|
+
# Subscription object with filtering capabilities
|
|
59
|
+
#
|
|
60
|
+
# Encapsulates a callback with optional filters. Filters can match
|
|
61
|
+
# against agent name, event type, swarm_id, or any event field.
|
|
62
|
+
class Subscription
|
|
63
|
+
attr_reader :id, :filter, :callback
|
|
64
|
+
|
|
65
|
+
# Initialize a subscription
|
|
66
|
+
#
|
|
67
|
+
# @param filter [Hash] Filter criteria
|
|
68
|
+
# @param callback [Proc] Block to call when event matches
|
|
69
|
+
def initialize(filter: {}, &callback)
|
|
70
|
+
@id = SecureRandom.uuid
|
|
71
|
+
@filter = normalize_filter(filter)
|
|
72
|
+
@callback = callback
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Check if event matches filter criteria
|
|
76
|
+
#
|
|
77
|
+
# Empty filter matches all events. Multiple filter keys are AND'd together.
|
|
78
|
+
#
|
|
79
|
+
# @param event [Hash] Event entry with :type, :agent, etc.
|
|
80
|
+
# @return [Boolean] True if event matches filter
|
|
81
|
+
def matches?(event)
|
|
82
|
+
return true if @filter.empty?
|
|
83
|
+
|
|
84
|
+
@filter.all? do |key, matcher|
|
|
85
|
+
value = event[key]
|
|
86
|
+
case matcher
|
|
87
|
+
when Array
|
|
88
|
+
# Match if value is in array (handles symbols/strings)
|
|
89
|
+
matcher.include?(value) || matcher.map(&:to_s).include?(value.to_s)
|
|
90
|
+
when Regexp
|
|
91
|
+
# Regex matching
|
|
92
|
+
value.to_s.match?(matcher)
|
|
93
|
+
when Proc
|
|
94
|
+
# Custom matcher
|
|
95
|
+
matcher.call(value)
|
|
96
|
+
else
|
|
97
|
+
# Exact match (handles symbols/strings)
|
|
98
|
+
matcher == value || matcher.to_s == value.to_s
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# Normalize filter keys to symbols for consistent matching
|
|
106
|
+
#
|
|
107
|
+
# @param filter [Hash] Raw filter
|
|
108
|
+
# @return [Hash] Normalized filter with symbol keys
|
|
109
|
+
def normalize_filter(filter)
|
|
110
|
+
filter.transform_keys(&:to_sym)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
38
114
|
class << self
|
|
39
|
-
#
|
|
115
|
+
# Subscribe to log events with optional filtering
|
|
40
116
|
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
117
|
+
# Registers a callback that will receive events matching the filter criteria.
|
|
118
|
+
# Returns a subscription ID that can be used to unsubscribe later.
|
|
43
119
|
#
|
|
120
|
+
# @param filter [Hash] Filter criteria (empty = all events)
|
|
121
|
+
# - :agent [Symbol, String, Array, Regexp] Agent name(s) to observe
|
|
122
|
+
# - :type [String, Array, Regexp] Event type(s) to receive
|
|
123
|
+
# - :swarm_id [String] Specific swarm instance
|
|
124
|
+
# - Any other key matches against event fields
|
|
44
125
|
# @yield [Hash] Log event entry
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
126
|
+
# @return [String] Subscription ID for unsubscribe
|
|
127
|
+
#
|
|
128
|
+
# @example Subscribe to all events
|
|
129
|
+
# LogCollector.subscribe { |event| puts event }
|
|
130
|
+
#
|
|
131
|
+
# @example Subscribe to specific agent's tool calls
|
|
132
|
+
# sub_id = LogCollector.subscribe(
|
|
133
|
+
# filter: { agent: :backend, type: /^tool_/ }
|
|
134
|
+
# ) do |event|
|
|
135
|
+
# puts "Backend used tool: #{event[:tool]}"
|
|
136
|
+
# end
|
|
137
|
+
#
|
|
138
|
+
# @example Subscribe to multiple agents
|
|
139
|
+
# LogCollector.subscribe(
|
|
140
|
+
# filter: { agent: [:backend, :frontend], type: "agent_stop" }
|
|
141
|
+
# ) { |e| record_completion(e) }
|
|
142
|
+
def subscribe(filter: {}, &block)
|
|
143
|
+
subscription = Subscription.new(filter: filter, &block)
|
|
144
|
+
subscriptions << subscription
|
|
145
|
+
subscription.id
|
|
48
146
|
end
|
|
49
147
|
|
|
50
|
-
#
|
|
148
|
+
# Unsubscribe by ID
|
|
149
|
+
#
|
|
150
|
+
# Removes a subscription to prevent memory leaks and stop receiving events.
|
|
151
|
+
#
|
|
152
|
+
# @param subscription_id [String] ID returned from subscribe
|
|
153
|
+
# @return [Subscription, nil] Removed subscription or nil if not found
|
|
154
|
+
def unsubscribe(subscription_id)
|
|
155
|
+
index = subscriptions.find_index { |s| s.id == subscription_id }
|
|
156
|
+
return unless index
|
|
157
|
+
|
|
158
|
+
subscriptions.delete_at(index)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Clear all subscriptions
|
|
162
|
+
#
|
|
163
|
+
# Removes all subscriptions. Useful for testing or execution cleanup.
|
|
164
|
+
#
|
|
165
|
+
# @return [void]
|
|
166
|
+
def clear_subscriptions
|
|
167
|
+
subscriptions.clear
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Get current subscription count
|
|
171
|
+
#
|
|
172
|
+
# @return [Integer] Number of active subscriptions
|
|
173
|
+
def subscription_count
|
|
174
|
+
subscriptions.size
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Emit an event to all matching subscribers
|
|
51
178
|
#
|
|
52
179
|
# Automatically adds a timestamp if one doesn't exist.
|
|
53
|
-
#
|
|
180
|
+
# Errors in individual subscribers are isolated - one bad subscriber
|
|
181
|
+
# won't prevent others from receiving events.
|
|
54
182
|
#
|
|
55
183
|
# @param entry [Hash] Log event entry
|
|
56
184
|
# @return [void]
|
|
57
185
|
def emit(entry)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
186
|
+
entry_with_timestamp = ensure_timestamp(entry)
|
|
187
|
+
|
|
188
|
+
subscriptions.each do |subscription|
|
|
189
|
+
next unless subscription.matches?(entry_with_timestamp)
|
|
190
|
+
|
|
191
|
+
begin
|
|
192
|
+
subscription.callback.call(entry_with_timestamp)
|
|
193
|
+
rescue StandardError => e
|
|
194
|
+
# Error isolation - don't let one subscriber break others
|
|
195
|
+
RubyLLM.logger.error("SwarmSDK: Subscription #{subscription.id} error: #{e.message}")
|
|
196
|
+
end
|
|
66
197
|
end
|
|
67
198
|
end
|
|
68
199
|
|
|
69
|
-
# Reset the collector (clears
|
|
200
|
+
# Reset the collector (clears subscriptions for next execution)
|
|
70
201
|
#
|
|
71
202
|
# @return [void]
|
|
72
203
|
def reset!
|
|
73
|
-
Fiber[:
|
|
204
|
+
Fiber[:log_subscriptions] = []
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
# Get subscriptions from Fiber-local storage
|
|
210
|
+
#
|
|
211
|
+
# @return [Array<Subscription>] Current subscriptions
|
|
212
|
+
def subscriptions
|
|
213
|
+
Fiber[:log_subscriptions] ||= []
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Ensure event has a timestamp
|
|
217
|
+
#
|
|
218
|
+
# @param entry [Hash] Event entry
|
|
219
|
+
# @return [Hash] Entry with timestamp
|
|
220
|
+
def ensure_timestamp(entry)
|
|
221
|
+
return entry if entry.key?(:timestamp)
|
|
222
|
+
|
|
223
|
+
entry.merge(timestamp: Time.now.utc.iso8601(6))
|
|
74
224
|
end
|
|
75
225
|
end
|
|
76
226
|
end
|
data/lib/swarm_sdk/log_stream.rb
CHANGED
|
@@ -93,6 +93,35 @@ module SwarmSDK
|
|
|
93
93
|
def enabled?
|
|
94
94
|
!Fiber[:log_stream_emitter].nil?
|
|
95
95
|
end
|
|
96
|
+
|
|
97
|
+
# Emit an internal error event
|
|
98
|
+
#
|
|
99
|
+
# Provides consistent error event emission for all internal errors.
|
|
100
|
+
# These are errors that occur during execution but are handled gracefully
|
|
101
|
+
# (with fallback behavior) rather than causing failures.
|
|
102
|
+
#
|
|
103
|
+
# @param error [Exception] The caught exception
|
|
104
|
+
# @param source [String] Source module/class (e.g., "hook_triggers", "context_compactor")
|
|
105
|
+
# @param context [String] Specific operation context (e.g., "swarm_stop", "summarization")
|
|
106
|
+
# @param agent [Symbol, String, nil] Agent name if applicable
|
|
107
|
+
# @param metadata [Hash] Additional context data
|
|
108
|
+
# @return [void]
|
|
109
|
+
def emit_error(error, source:, context:, agent: nil, **metadata)
|
|
110
|
+
emit(
|
|
111
|
+
type: "internal_error",
|
|
112
|
+
source: source,
|
|
113
|
+
context: context,
|
|
114
|
+
agent: agent,
|
|
115
|
+
error_class: error.class.name,
|
|
116
|
+
error_message: error.message,
|
|
117
|
+
backtrace: error.backtrace&.first(5),
|
|
118
|
+
**metadata,
|
|
119
|
+
)
|
|
120
|
+
rescue StandardError
|
|
121
|
+
# Absolute fallback - if emit_error itself fails, don't break execution
|
|
122
|
+
# This should never happen, but we must be defensive
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
96
125
|
end
|
|
97
126
|
end
|
|
98
127
|
end
|