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