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,781 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmCLI
4
- module Formatters
5
- # HumanFormatter creates beautiful, detailed real-time output using reusable UI components.
6
- # Shows everything: agent thinking, tool calls with arguments, results, responses.
7
- # Uses clean component architecture for maintainability and testability.
8
- #
9
- # Modes:
10
- # - :non_interactive - Full headers, task prompt, complete summary (for single execution)
11
- # - :interactive - Minimal output for REPL (headers shown in welcome screen)
12
- class HumanFormatter
13
- attr_reader :spinner_manager
14
-
15
- def initialize(output: $stdout, quiet: false, truncate: false, verbose: false, mode: :non_interactive)
16
- @output = output
17
- @quiet = quiet
18
- @truncate = truncate
19
- @verbose = verbose
20
- @mode = mode
21
-
22
- # Initialize Pastel with TTY detection
23
- @pastel = Pastel.new(enabled: output.tty?)
24
-
25
- # Initialize state managers
26
- @color_cache = SwarmCLI::UI::State::AgentColorCache.new
27
- @depth_tracker = SwarmCLI::UI::State::DepthTracker.new
28
- @usage_tracker = SwarmCLI::UI::State::UsageTracker.new
29
- @spinner_manager = SwarmCLI::UI::State::SpinnerManager.new
30
-
31
- # Initialize components
32
- @divider = SwarmCLI::UI::Components::Divider.new(pastel: @pastel, terminal_width: TTY::Screen.width)
33
- @agent_badge = SwarmCLI::UI::Components::AgentBadge.new(pastel: @pastel, color_cache: @color_cache)
34
- @content_block = SwarmCLI::UI::Components::ContentBlock.new(pastel: @pastel)
35
- @panel = SwarmCLI::UI::Components::Panel.new(pastel: @pastel)
36
-
37
- # Initialize event renderer
38
- @event_renderer = SwarmCLI::UI::Renderers::EventRenderer.new(
39
- pastel: @pastel,
40
- agent_badge: @agent_badge,
41
- depth_tracker: @depth_tracker,
42
- )
43
-
44
- # Track last context percentage for warnings
45
- @last_context_percentage = {}
46
-
47
- # Start time tracking
48
- @start_time = nil
49
- end
50
-
51
- # Called when swarm execution starts
52
- def on_start(config_path:, swarm_name:, lead_agent:, prompt:)
53
- @start_time = Time.now
54
-
55
- # Only show headers in non-interactive mode
56
- if @mode == :non_interactive
57
- print_header(swarm_name, lead_agent)
58
- print_prompt(prompt)
59
- @output.puts @divider.full
60
- @output.puts @pastel.bold("#{SwarmCLI::UI::Icons::INFO} Execution Log:")
61
- @output.puts
62
- end
63
- end
64
-
65
- # Called for each log entry from SwarmSDK
66
- def on_log(entry)
67
- return if @quiet
68
-
69
- case entry[:type]
70
- when "user_prompt"
71
- handle_user_request(entry)
72
- when "agent_step"
73
- handle_agent_step(entry)
74
- when "agent_stop"
75
- handle_agent_stop(entry)
76
- when "tool_call"
77
- handle_tool_call(entry)
78
- when "tool_result"
79
- handle_tool_result(entry)
80
- when "agent_delegation"
81
- handle_agent_delegation(entry)
82
- when "delegation_result"
83
- handle_delegation_result(entry)
84
- when "context_limit_warning"
85
- handle_context_warning(entry)
86
- when "model_lookup_warning"
87
- handle_model_lookup_warning(entry)
88
- when "compression_started"
89
- handle_compression_started(entry)
90
- when "compression_completed"
91
- handle_compression_completed(entry)
92
- when "hook_executed"
93
- handle_hook_executed(entry)
94
- when "breakpoint_enter"
95
- handle_breakpoint_enter(entry)
96
- when "breakpoint_exit"
97
- handle_breakpoint_exit(entry)
98
- when "llm_retry_attempt"
99
- handle_llm_retry_attempt(entry)
100
- when "llm_retry_exhausted"
101
- handle_llm_retry_exhausted(entry)
102
- end
103
- end
104
-
105
- # Called when swarm execution completes successfully
106
- def on_success(result:)
107
- # Defensive: ensure all spinners are stopped before showing result
108
- @spinner_manager.stop_all
109
-
110
- if @mode == :non_interactive
111
- # Full result display with summary
112
- @output.puts
113
- @output.puts @divider.full
114
- end
115
-
116
- # Print result (handles mode internally)
117
- print_result(result)
118
-
119
- # Only print summary in non-interactive mode
120
- print_summary(result) if @mode == :non_interactive
121
- end
122
-
123
- # Called when swarm execution fails
124
- def on_error(error:, duration: nil)
125
- # Defensive: ensure all spinners are stopped before showing error
126
- @spinner_manager.stop_all
127
-
128
- @output.puts
129
- @output.puts @divider.full
130
- print_error(error)
131
- @output.puts @divider.full
132
- end
133
-
134
- private
135
-
136
- def handle_user_request(entry)
137
- agent = entry[:agent]
138
- @usage_tracker.track_agent(agent)
139
- @usage_tracker.track_llm_request(entry[:usage])
140
-
141
- # Stop any delegation waiting spinner (in case this agent was delegated to)
142
- unless @quiet
143
- delegation_spinner = "delegation_#{agent}".to_sym
144
- @spinner_manager.stop(delegation_spinner) if @spinner_manager.active?(delegation_spinner)
145
- end
146
-
147
- # Render agent thinking line
148
- @output.puts @event_renderer.agent_thinking(
149
- agent: agent,
150
- model: entry[:model],
151
- timestamp: entry[:timestamp],
152
- )
153
-
154
- # Show tools available
155
- if entry[:tools]&.any?
156
- @output.puts @event_renderer.tools_available(entry[:tools], indent: @depth_tracker.get(agent))
157
- end
158
-
159
- # Show delegation options
160
- if entry[:delegates_to]&.any?
161
- @output.puts @event_renderer.delegates_to(
162
- entry[:delegates_to],
163
- indent: @depth_tracker.get(agent),
164
- color_cache: @color_cache,
165
- )
166
- end
167
-
168
- @output.puts
169
-
170
- # Start spinner for agent thinking
171
- unless @quiet
172
- spinner_key = "agent_#{agent}".to_sym
173
- @spinner_manager.start(spinner_key, "#{agent} is thinking...")
174
- end
175
- end
176
-
177
- def handle_agent_step(entry)
178
- agent = entry[:agent]
179
- indent_level = @depth_tracker.get(agent)
180
-
181
- # Stop agent thinking spinner
182
- unless @quiet
183
- spinner_key = "agent_#{agent}".to_sym
184
- @spinner_manager.stop(spinner_key)
185
- end
186
-
187
- # Track usage
188
- if entry[:usage]
189
- @usage_tracker.track_llm_request(entry[:usage])
190
- @last_context_percentage[agent] = entry[:usage][:tokens_used_percentage]
191
-
192
- # Render usage stats
193
- @output.puts @event_renderer.usage_stats(
194
- tokens: entry[:usage][:total_tokens] || 0,
195
- cost: entry[:usage][:total_cost] || 0.0,
196
- context_pct: entry[:usage][:tokens_used_percentage],
197
- remaining: entry[:usage][:tokens_remaining],
198
- cumulative: entry[:usage][:cumulative_total_tokens],
199
- indent: indent_level,
200
- )
201
- end
202
-
203
- # Display thinking text (if present)
204
- if entry[:content] && !entry[:content].empty?
205
- thinking = @event_renderer.thinking_text(entry[:content], indent: indent_level)
206
- @output.puts thinking unless thinking.empty?
207
- @output.puts if thinking && !thinking.empty?
208
- end
209
-
210
- # Show tool request summary
211
- tool_count = entry[:tool_calls]&.size || 0
212
- if tool_count > 0
213
- indent = @depth_tracker.indent(agent)
214
- @output.puts "#{indent} #{@pastel.dim("→ Requesting #{tool_count} tool#{"s" if tool_count > 1}...")}"
215
- end
216
-
217
- @output.puts
218
- @output.puts @divider.event(indent: indent_level)
219
- end
220
-
221
- def handle_agent_stop(entry)
222
- agent = entry[:agent]
223
- indent_level = @depth_tracker.get(agent)
224
-
225
- # Stop agent thinking spinner with success
226
- unless @quiet
227
- spinner_key = "agent_#{agent}".to_sym
228
- @spinner_manager.success(spinner_key, "completed")
229
- end
230
-
231
- # Track usage
232
- if entry[:usage]
233
- @usage_tracker.track_llm_request(entry[:usage])
234
- @last_context_percentage[agent] = entry[:usage][:tokens_used_percentage]
235
-
236
- # Render usage stats
237
- @output.puts @event_renderer.usage_stats(
238
- tokens: entry[:usage][:total_tokens] || 0,
239
- cost: entry[:usage][:total_cost] || 0.0,
240
- context_pct: entry[:usage][:tokens_used_percentage],
241
- remaining: entry[:usage][:tokens_remaining],
242
- cumulative: entry[:usage][:cumulative_total_tokens],
243
- indent: indent_level,
244
- )
245
- end
246
-
247
- # Display final response (only for top-level agent in non-interactive mode)
248
- # In interactive mode, response is shown by print_result to avoid duplication
249
- if entry[:content] && !entry[:content].empty? && indent_level.zero? && @mode == :non_interactive
250
- @output.puts @event_renderer.agent_response(
251
- agent: agent,
252
- timestamp: entry[:timestamp],
253
- )
254
-
255
- # Render response content
256
- indent = @depth_tracker.indent(agent)
257
- response_lines = entry[:content].split("\n")
258
-
259
- if @truncate && response_lines.length > 12
260
- response_lines.first(12).each { |line| @output.puts "#{indent} #{line}" }
261
- @output.puts "#{indent} #{@pastel.dim("... (#{response_lines.length - 12} more lines)")}"
262
- else
263
- response_lines.each { |line| @output.puts "#{indent} #{line}" }
264
- end
265
- end
266
-
267
- @output.puts
268
- @output.puts @event_renderer.agent_completed(agent: agent)
269
- @output.puts
270
- @output.puts @divider.event(indent: indent_level)
271
- end
272
-
273
- def handle_tool_call(entry)
274
- agent = entry[:agent]
275
- @usage_tracker.track_tool_call(tool_call_id: entry[:tool_call_id], tool_name: entry[:tool])
276
-
277
- # Special handling for Think tool - show as thoughts, not as a tool call
278
- if entry[:tool] == "Think" && entry[:arguments] && entry[:arguments]["thoughts"]
279
- thoughts = entry[:arguments]["thoughts"]
280
- thinking = @event_renderer.thinking_text(thoughts, indent: @depth_tracker.get(agent))
281
- @output.puts thinking unless thinking.empty?
282
- @output.puts
283
- # Don't show spinner for Think tool
284
- return
285
- end
286
-
287
- # Render tool call event
288
- @output.puts @event_renderer.tool_call(
289
- agent: agent,
290
- tool: entry[:tool],
291
- timestamp: entry[:timestamp],
292
- )
293
-
294
- # Show arguments (skip TodoWrite unless verbose)
295
- args = entry[:arguments]
296
- show_args = args && !args.empty?
297
- show_args &&= entry[:tool] != "TodoWrite" || @verbose
298
-
299
- if show_args
300
- @output.puts @event_renderer.tool_arguments(
301
- args,
302
- indent: @depth_tracker.get(agent),
303
- truncate: @truncate,
304
- )
305
- end
306
-
307
- @output.puts
308
-
309
- # Start spinner for tool execution
310
- unless @quiet || entry[:tool] == "TodoWrite"
311
- spinner_key = "tool_#{entry[:tool_call_id]}".to_sym
312
- @spinner_manager.start(spinner_key, "Executing #{entry[:tool]}...")
313
- end
314
- end
315
-
316
- def handle_tool_result(entry)
317
- agent = entry[:agent]
318
- tool_name = entry[:tool] || @usage_tracker.tool_name_for(entry[:tool_call_id])
319
-
320
- # Special handling for Think tool - skip showing result (already shown as thoughts)
321
- if tool_name == "Think"
322
- # Don't show anything - thoughts were already displayed in handle_tool_call
323
- # Start spinner for agent processing
324
- unless @quiet
325
- spinner_key = "agent_#{agent}".to_sym
326
- indent = @depth_tracker.indent(agent)
327
- @spinner_manager.start(spinner_key, "#{indent}#{agent} is processing...")
328
- end
329
- return
330
- end
331
-
332
- # Stop tool spinner with success
333
- unless @quiet || tool_name == "TodoWrite"
334
- spinner_key = "tool_#{entry[:tool_call_id]}".to_sym
335
- @spinner_manager.success(spinner_key, "completed")
336
- end
337
-
338
- # Special handling for TodoWrite
339
- if tool_name == "TodoWrite"
340
- display_todo_list(agent, entry[:timestamp])
341
- else
342
- @output.puts @event_renderer.tool_result(
343
- agent: agent,
344
- timestamp: entry[:timestamp],
345
- tool: tool_name,
346
- )
347
-
348
- # Render result content
349
- if entry[:result].is_a?(String) && !entry[:result].empty?
350
- result_text = @event_renderer.tool_result_content(
351
- entry[:result],
352
- indent: @depth_tracker.get(agent),
353
- truncate: !@verbose,
354
- )
355
- @output.puts result_text unless result_text.empty?
356
- end
357
- end
358
-
359
- @output.puts
360
- @output.puts @divider.event(indent: @depth_tracker.get(agent))
361
-
362
- # Start spinner for agent processing tool result
363
- # The agent will determine what to do next (more tools or finish)
364
- # This spinner will be stopped by the next agent_step or agent_stop event
365
- unless @quiet
366
- spinner_key = "agent_#{agent}".to_sym
367
- indent = @depth_tracker.indent(agent)
368
- @spinner_manager.start(spinner_key, "#{indent}#{agent} is processing...")
369
- end
370
- end
371
-
372
- def handle_agent_delegation(entry)
373
- @usage_tracker.track_tool_call
374
-
375
- @output.puts @event_renderer.delegation(
376
- from: entry[:agent],
377
- to: entry[:delegate_to],
378
- timestamp: entry[:timestamp],
379
- )
380
- @output.puts
381
-
382
- # Show arguments if present
383
- if entry[:arguments] && !entry[:arguments].empty?
384
- @output.puts @event_renderer.tool_arguments(
385
- entry[:arguments],
386
- indent: @depth_tracker.get(entry[:agent]),
387
- truncate: @truncate,
388
- )
389
- end
390
-
391
- @output.puts
392
-
393
- # Start spinner waiting for delegated agent
394
- unless @quiet
395
- spinner_key = "delegation_#{entry[:delegate_to]}".to_sym
396
- indent = @depth_tracker.indent(entry[:agent])
397
- @spinner_manager.start(spinner_key, "#{indent}Waiting for #{entry[:delegate_to]}...")
398
- end
399
- end
400
-
401
- def handle_delegation_result(entry)
402
- @output.puts @event_renderer.delegation_result(
403
- from: entry[:delegate_from],
404
- to: entry[:agent],
405
- timestamp: entry[:timestamp],
406
- )
407
-
408
- # Render result content
409
- if entry[:result].is_a?(String) && !entry[:result].empty?
410
- result_text = @event_renderer.tool_result_content(
411
- entry[:result],
412
- indent: @depth_tracker.get(entry[:agent]),
413
- truncate: !@verbose,
414
- )
415
- @output.puts result_text unless result_text.empty?
416
- end
417
-
418
- @output.puts
419
- @output.puts @divider.event(indent: @depth_tracker.get(entry[:agent]))
420
-
421
- # Start spinner for agent processing delegation result
422
- unless @quiet
423
- spinner_key = "agent_#{entry[:agent]}".to_sym
424
- indent = @depth_tracker.indent(entry[:agent])
425
- @spinner_manager.start(spinner_key, "#{indent}#{entry[:agent]} is processing...")
426
- end
427
- end
428
-
429
- def handle_context_warning(entry)
430
- agent = entry[:agent]
431
- threshold = entry[:threshold]
432
- current_usage = entry[:current_usage]
433
- tokens_remaining = entry[:tokens_remaining]
434
-
435
- # Determine warning severity
436
- type = threshold == "90%" ? :error : :warning
437
-
438
- @output.puts @panel.render(
439
- type: type,
440
- title: "CONTEXT WARNING #{@agent_badge.render(agent)}",
441
- lines: [
442
- @pastel.public_send((type == :error ? :red : :yellow), "Context usage: #{current_usage} (threshold: #{threshold})"),
443
- @pastel.dim("Tokens remaining: #{SwarmCLI::UI::Formatters::Number.format(tokens_remaining)}"),
444
- ],
445
- indent: @depth_tracker.get(agent),
446
- )
447
- end
448
-
449
- def handle_model_lookup_warning(entry)
450
- agent = entry[:agent]
451
- model = entry[:model]
452
- error_message = entry[:error_message]
453
- suggestions = entry[:suggestions] || []
454
-
455
- lines = [
456
- @pastel.yellow("Model '#{model}' not found in registry"),
457
- ]
458
-
459
- if suggestions.any?
460
- lines << @pastel.dim("Did you mean one of these?")
461
- suggestions.each do |suggestion|
462
- model_id = suggestion[:id] || suggestion["id"]
463
- context = suggestion[:context_window] || suggestion["context_window"]
464
- context_display = context ? " (#{SwarmCLI::UI::Formatters::Number.format(context)} tokens)" : ""
465
- lines << " #{@pastel.cyan("•")} #{@pastel.white(model_id)}#{@pastel.dim(context_display)}"
466
- end
467
- else
468
- lines << @pastel.dim("Error: #{error_message}")
469
- end
470
-
471
- lines << @pastel.dim("Context tracking unavailable for this model.")
472
-
473
- @output.puts @panel.render(
474
- type: :warning,
475
- title: "MODEL WARNING #{@agent_badge.render(agent)}",
476
- lines: lines,
477
- indent: 0, # Always at root level (warnings shown at boot, not during execution)
478
- )
479
- end
480
-
481
- def handle_compression_started(entry)
482
- agent = entry[:agent]
483
- message_count = entry[:message_count]
484
- estimated_tokens = entry[:estimated_tokens]
485
-
486
- @output.puts @panel.render(
487
- type: :info,
488
- title: "CONTEXT COMPRESSION #{@agent_badge.render(agent)}",
489
- lines: [
490
- @pastel.dim("Compressing #{message_count} messages (~#{SwarmCLI::UI::Formatters::Number.format(estimated_tokens)} tokens)..."),
491
- ],
492
- indent: @depth_tracker.get(agent),
493
- )
494
- end
495
-
496
- def handle_compression_completed(entry)
497
- agent = entry[:agent]
498
- original_messages = entry[:original_message_count]
499
- compressed_messages = entry[:compressed_message_count]
500
- messages_removed = entry[:messages_removed]
501
- original_tokens = entry[:original_tokens]
502
- compressed_tokens = entry[:compressed_tokens]
503
- compression_ratio = entry[:compression_ratio]
504
- time_taken = entry[:time_taken]
505
-
506
- @output.puts @panel.render(
507
- type: :success,
508
- title: "COMPRESSION COMPLETE #{@agent_badge.render(agent)}",
509
- lines: [
510
- "#{@pastel.dim("Messages:")} #{original_messages} → #{compressed_messages} #{@pastel.green("(-#{messages_removed})")}",
511
- "#{@pastel.dim("Tokens:")} #{SwarmCLI::UI::Formatters::Number.format(original_tokens)} → #{SwarmCLI::UI::Formatters::Number.format(compressed_tokens)} #{@pastel.green("(#{(compression_ratio * 100).round(1)}%)")}",
512
- "#{@pastel.dim("Time taken:")} #{SwarmCLI::UI::Formatters::Time.duration(time_taken)}",
513
- ],
514
- indent: @depth_tracker.get(agent),
515
- )
516
- end
517
-
518
- def handle_hook_executed(entry)
519
- hook_event = entry[:hook_event]
520
- agent = entry[:agent]
521
- success = entry[:success]
522
- blocked = entry[:blocked]
523
- stderr = entry[:stderr]
524
- exit_code = entry[:exit_code]
525
-
526
- @output.puts @event_renderer.hook_executed(
527
- hook_event: hook_event,
528
- agent: agent,
529
- timestamp: entry[:timestamp],
530
- success: success,
531
- blocked: blocked,
532
- )
533
-
534
- # Show stderr if present
535
- if stderr && !stderr.empty?
536
- indent = @depth_tracker.indent(agent)
537
-
538
- if blocked && hook_event == "user_prompt"
539
- @output.puts
540
- @output.puts "#{indent} #{@pastel.bold.red("⛔ Prompt Blocked by Hook:")}"
541
- stderr.lines.each { |line| @output.puts "#{indent} #{@pastel.red(line.chomp)}" }
542
- @output.puts "#{indent} #{@pastel.dim("(Prompt was not sent to the agent)")}"
543
- elsif blocked
544
- @output.puts "#{indent} #{@pastel.red("Blocked:")} #{@pastel.red(stderr)}"
545
- else
546
- @output.puts "#{indent} #{@pastel.yellow("Message:")} #{@pastel.dim(stderr)}"
547
- end
548
- end
549
-
550
- # Show exit code in verbose mode
551
- if @verbose && exit_code
552
- indent = @depth_tracker.indent(agent)
553
- code_color = if exit_code.zero?
554
- :green
555
- else
556
- (exit_code == 2 ? :red : :yellow)
557
- end
558
- @output.puts "#{indent} #{@pastel.dim("Exit code:")} #{@pastel.public_send(code_color, exit_code)}"
559
- end
560
-
561
- @output.puts
562
- end
563
-
564
- def handle_breakpoint_enter(entry)
565
- agent = entry[:agent]
566
- event = entry[:event]
567
-
568
- # Pause all spinners to allow clean interactive debugging
569
- @spinner_manager.pause_all
570
-
571
- # Show debugging notice
572
- @output.puts
573
- @output.puts @pastel.yellow("#{SwarmCLI::UI::Icons::THINKING} Breakpoint: Entering interactive debugging (#{event} hook)")
574
- @output.puts @pastel.dim(" Agent: #{agent}")
575
- @output.puts @pastel.dim(" Type 'exit' to continue execution")
576
- @output.puts
577
- end
578
-
579
- def handle_breakpoint_exit(entry)
580
- # Resume all spinners after debugging
581
- @spinner_manager.resume_all
582
-
583
- @output.puts
584
- @output.puts @pastel.green("#{SwarmCLI::UI::Icons::SUCCESS} Breakpoint: Resuming execution")
585
- @output.puts
586
- end
587
-
588
- def handle_llm_retry_attempt(entry)
589
- agent = entry[:agent]
590
- attempt = entry[:attempt]
591
- max_retries = entry[:max_retries]
592
- error_class = entry[:error_class]
593
- error_message = entry[:error_message]
594
- retry_delay = entry[:retry_delay]
595
-
596
- # Stop agent thinking spinner (if active)
597
- unless @quiet
598
- spinner_key = "agent_#{agent}".to_sym
599
- @spinner_manager.stop(spinner_key) if @spinner_manager.active?(spinner_key)
600
- end
601
-
602
- lines = [
603
- @pastel.yellow("LLM API request failed (attempt #{attempt}/#{max_retries})"),
604
- @pastel.dim("Error: #{error_class}: #{error_message}"),
605
- @pastel.dim("Retrying in #{retry_delay}s..."),
606
- ]
607
-
608
- @output.puts @panel.render(
609
- type: :warning,
610
- title: "RETRY #{@agent_badge.render(agent)}",
611
- lines: lines,
612
- indent: @depth_tracker.get(agent),
613
- )
614
-
615
- # Restart spinner for next attempt
616
- unless @quiet
617
- spinner_key = "agent_#{agent}".to_sym
618
- @spinner_manager.start(spinner_key, "#{agent} is retrying...")
619
- end
620
- end
621
-
622
- def handle_llm_retry_exhausted(entry)
623
- agent = entry[:agent]
624
- attempts = entry[:attempts]
625
- error_class = entry[:error_class]
626
- error_message = entry[:error_message]
627
-
628
- # Stop agent thinking spinner (if active)
629
- unless @quiet
630
- spinner_key = "agent_#{agent}".to_sym
631
- @spinner_manager.stop(spinner_key) if @spinner_manager.active?(spinner_key)
632
- end
633
-
634
- lines = [
635
- @pastel.red("LLM API request failed after #{attempts} attempts"),
636
- @pastel.dim("Error: #{error_class}: #{error_message}"),
637
- @pastel.dim("No more retries available"),
638
- ]
639
-
640
- @output.puts @panel.render(
641
- type: :error,
642
- title: "RETRY EXHAUSTED #{@agent_badge.render(agent)}",
643
- lines: lines,
644
- indent: @depth_tracker.get(agent),
645
- )
646
- end
647
-
648
- def display_todo_list(agent, timestamp)
649
- todos = SwarmSDK::Tools::Stores::TodoManager.get_todos(agent.to_sym)
650
- indent = @depth_tracker.indent(agent)
651
- time = SwarmCLI::UI::Formatters::Time.timestamp(timestamp)
652
-
653
- if todos.empty?
654
- @output.puts "#{indent}#{@pastel.dim(time)} #{@pastel.cyan("#{SwarmCLI::UI::Icons::BULLET} Todo list")} updated (empty)"
655
- return
656
- end
657
-
658
- @output.puts "#{indent}#{@pastel.dim(time)} #{@pastel.cyan("#{SwarmCLI::UI::Icons::BULLET} Todo list")} updated:"
659
- @output.puts
660
-
661
- todos.each_with_index do |todo, index|
662
- status = todo[:status] || todo["status"]
663
- content = todo[:content] || todo["content"]
664
- num = index + 1
665
-
666
- line = case status
667
- when "completed"
668
- "#{indent} #{@pastel.dim("#{num}.")} #{@pastel.dim.strikethrough(content)}"
669
- when "in_progress"
670
- "#{indent} #{@pastel.bold.yellow("#{num}.")} #{@pastel.bold(content)}"
671
- when "pending"
672
- "#{indent} #{@pastel.white("#{num}.")} #{content}"
673
- else
674
- "#{indent} #{num}. #{content}"
675
- end
676
-
677
- @output.puts line
678
- end
679
- end
680
-
681
- def print_header(swarm_name, lead_agent)
682
- @output.puts
683
- @output.puts @pastel.bold.bright_cyan("#{SwarmCLI::UI::Icons::SPARKLES} SwarmSDK - AI Agent Orchestration #{SwarmCLI::UI::Icons::SPARKLES}")
684
- @output.puts @divider.full
685
- @output.puts "#{@pastel.bold("Swarm:")} #{@pastel.cyan(swarm_name)}"
686
- @output.puts "#{@pastel.bold("Lead Agent:")} #{@pastel.cyan(lead_agent)}"
687
- @output.puts
688
- end
689
-
690
- def print_prompt(prompt)
691
- @output.puts @pastel.bold("#{SwarmCLI::UI::Icons::THINKING} Task Prompt:")
692
- @output.puts @pastel.bright_white(prompt)
693
- @output.puts
694
- end
695
-
696
- def print_result(result)
697
- return unless result.content && !result.content.empty?
698
-
699
- # Interactive mode: Just show the response content directly
700
- if @mode == :interactive
701
- # Render markdown if content looks like markdown
702
- content_to_display = if looks_like_markdown?(result.content)
703
- begin
704
- TTY::Markdown.parse(result.content)
705
- rescue StandardError
706
- result.content
707
- end
708
- else
709
- result.content
710
- end
711
-
712
- @output.puts content_to_display
713
- @output.puts
714
- return
715
- end
716
-
717
- # Non-interactive mode: Full result display with header and dividers
718
- @output.puts
719
- @output.puts @pastel.bold.green("#{SwarmCLI::UI::Icons::SUCCESS} Execution Complete")
720
- @output.puts
721
- @output.puts @pastel.bold("#{SwarmCLI::UI::Icons::RESPONSE} Final Response from #{@agent_badge.render(result.agent)}:")
722
- @output.puts
723
- @output.puts @divider.full
724
-
725
- # Render markdown if content looks like markdown
726
- content_to_display = if looks_like_markdown?(result.content)
727
- begin
728
- TTY::Markdown.parse(result.content)
729
- rescue StandardError
730
- result.content
731
- end
732
- else
733
- result.content
734
- end
735
-
736
- @output.puts content_to_display
737
- @output.puts @divider.full
738
- @output.puts
739
- end
740
-
741
- def print_summary(result)
742
- @output.puts @pastel.bold("#{SwarmCLI::UI::Icons::INFO} Execution Summary:")
743
- @output.puts
744
-
745
- # Agents used (colored list)
746
- agents_display = @agent_badge.render_list(@usage_tracker.agents)
747
- @output.puts " #{SwarmCLI::UI::Icons::AGENT} #{@pastel.bold("Agents used:")} #{agents_display}"
748
-
749
- # Metrics
750
- @output.puts " #{SwarmCLI::UI::Icons::LLM} #{@pastel.bold("LLM Requests:")} #{result.llm_requests}"
751
- @output.puts " #{SwarmCLI::UI::Icons::TOOL} #{@pastel.bold("Tool Calls:")} #{result.tool_calls_count}"
752
- @output.puts " #{SwarmCLI::UI::Icons::TOKENS} #{@pastel.bold("Total Tokens:")} #{SwarmCLI::UI::Formatters::Number.format(result.total_tokens)}"
753
- @output.puts " #{SwarmCLI::UI::Icons::COST} #{@pastel.bold("Total Cost:")} #{SwarmCLI::UI::Formatters::Cost.format(result.total_cost, pastel: @pastel)}"
754
- @output.puts " #{SwarmCLI::UI::Icons::TIME} #{@pastel.bold("Duration:")} #{SwarmCLI::UI::Formatters::Time.duration(result.duration)}"
755
-
756
- @output.puts
757
- end
758
-
759
- def print_error(error)
760
- @output.puts
761
- @output.puts @pastel.bold.red("#{SwarmCLI::UI::Icons::ERROR} Execution Failed")
762
- @output.puts
763
- @output.puts @pastel.red("Error: #{error.class.name}")
764
- @output.puts @pastel.red(error.message)
765
- @output.puts
766
-
767
- return unless error.backtrace
768
-
769
- @output.puts @pastel.dim("Backtrace:")
770
- error.backtrace.first(5).each do |line|
771
- @output.puts @pastel.dim(" #{line}")
772
- end
773
- @output.puts
774
- end
775
-
776
- def looks_like_markdown?(text)
777
- text.match?(/^#+\s|^\*\s|^-\s|^\d+\.\s|```|\[.+\]\(.+\)/)
778
- end
779
- end
780
- end
781
- end