claude_swarm 1.0.1 → 1.0.2
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/.claude/commands/release.md +1 -1
- data/.claude/hooks/lint-code-files.rb +65 -0
- data/.rubocop.yml +22 -2
- data/CHANGELOG.md +14 -1
- data/CLAUDE.md +1 -1
- data/CONTRIBUTING.md +69 -0
- data/README.md +27 -2
- data/Rakefile +71 -3
- data/analyze_coverage.rb +94 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
- data/docs/v2/README.md +308 -0
- data/docs/v2/guides/claude-code-agents.md +262 -0
- data/docs/v2/guides/complete-tutorial.md +3088 -0
- data/docs/v2/guides/getting-started.md +1456 -0
- data/docs/v2/guides/memory-adapters.md +998 -0
- data/docs/v2/guides/plugins.md +816 -0
- data/docs/v2/guides/quick-start-cli.md +1745 -0
- data/docs/v2/guides/rails-integration.md +1902 -0
- data/docs/v2/guides/swarm-memory.md +599 -0
- data/docs/v2/reference/cli.md +729 -0
- data/docs/v2/reference/ruby-dsl.md +2154 -0
- data/docs/v2/reference/yaml.md +1835 -0
- data/docs-team-swarm.yml +2222 -0
- data/examples/learning-assistant/assistant.md +7 -0
- data/examples/learning-assistant/example-memories/concept-example.md +90 -0
- data/examples/learning-assistant/example-memories/experience-example.md +66 -0
- data/examples/learning-assistant/example-memories/fact-example.md +76 -0
- data/examples/learning-assistant/example-memories/memory-index.md +78 -0
- data/examples/learning-assistant/example-memories/skill-example.md +168 -0
- data/examples/learning-assistant/learning_assistant.rb +34 -0
- data/examples/learning-assistant/learning_assistant.yml +20 -0
- data/examples/v2/dsl/01_basic.rb +44 -0
- data/examples/v2/dsl/02_core_parameters.rb +59 -0
- data/examples/v2/dsl/03_capabilities.rb +71 -0
- data/examples/v2/dsl/04_llm_parameters.rb +56 -0
- data/examples/v2/dsl/05_advanced_flags.rb +73 -0
- data/examples/v2/dsl/06_permissions.rb +80 -0
- data/examples/v2/dsl/07_mcp_server.rb +62 -0
- data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
- data/examples/v2/dsl/09_agent_hooks.rb +67 -0
- data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
- data/examples/v2/dsl/11_delegation.rb +60 -0
- data/examples/v2/dsl/12_complete_integration.rb +137 -0
- data/examples/v2/file_tools_swarm.yml +102 -0
- data/examples/v2/hooks/01_basic_hooks.rb +133 -0
- data/examples/v2/hooks/02_usage_tracking.rb +201 -0
- data/examples/v2/hooks/03_production_monitoring.rb +429 -0
- data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
- data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
- data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
- data/examples/v2/hooks/swarm_summary.sh +44 -0
- data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
- data/examples/v2/hooks/validate_bash.rb +59 -0
- data/examples/v2/multi_directory_permissions.yml +221 -0
- data/examples/v2/node_context_demo.rb +127 -0
- data/examples/v2/node_workflow.rb +173 -0
- data/examples/v2/path_resolution_demo.rb +216 -0
- data/examples/v2/simple-swarm-v2.rb +90 -0
- data/examples/v2/simple-swarm-v2.yml +62 -0
- data/examples/v2/swarm.yml +71 -0
- data/examples/v2/swarm_with_hooks.yml +61 -0
- data/examples/v2/swarm_with_hooks_simple.yml +25 -0
- data/examples/v2/think_tool_demo.rb +62 -0
- data/exe/swarm +6 -0
- data/lib/claude_swarm/claude_mcp_server.rb +0 -6
- data/lib/claude_swarm/cli.rb +10 -3
- data/lib/claude_swarm/commands/ps.rb +19 -20
- data/lib/claude_swarm/commands/show.rb +1 -1
- data/lib/claude_swarm/configuration.rb +10 -12
- data/lib/claude_swarm/mcp_generator.rb +10 -1
- data/lib/claude_swarm/orchestrator.rb +73 -49
- data/lib/claude_swarm/system_utils.rb +37 -11
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm/worktree_manager.rb +1 -0
- data/lib/claude_swarm/yaml_loader.rb +22 -0
- data/lib/claude_swarm.rb +6 -2
- data/lib/swarm_cli/cli.rb +201 -0
- data/lib/swarm_cli/command_registry.rb +61 -0
- data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
- data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
- data/lib/swarm_cli/commands/migrate.rb +55 -0
- data/lib/swarm_cli/commands/run.rb +173 -0
- data/lib/swarm_cli/config_loader.rb +97 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
- data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
- data/lib/swarm_cli/interactive_repl.rb +918 -0
- data/lib/swarm_cli/mcp_serve_options.rb +44 -0
- data/lib/swarm_cli/mcp_tools_options.rb +59 -0
- data/lib/swarm_cli/migrate_options.rb +54 -0
- data/lib/swarm_cli/migrator.rb +132 -0
- data/lib/swarm_cli/options.rb +151 -0
- data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
- data/lib/swarm_cli/ui/components/content_block.rb +120 -0
- data/lib/swarm_cli/ui/components/divider.rb +57 -0
- data/lib/swarm_cli/ui/components/panel.rb +62 -0
- data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
- data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
- data/lib/swarm_cli/ui/formatters/number.rb +58 -0
- data/lib/swarm_cli/ui/formatters/text.rb +77 -0
- data/lib/swarm_cli/ui/formatters/time.rb +73 -0
- data/lib/swarm_cli/ui/icons.rb +59 -0
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
- data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
- data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
- data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
- data/lib/swarm_cli/version.rb +5 -0
- data/lib/swarm_cli.rb +44 -0
- data/lib/swarm_memory/adapters/base.rb +141 -0
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
- data/lib/swarm_memory/chat_extension.rb +34 -0
- data/lib/swarm_memory/cli/commands.rb +306 -0
- data/lib/swarm_memory/core/entry.rb +37 -0
- data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
- data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
- data/lib/swarm_memory/core/path_normalizer.rb +75 -0
- data/lib/swarm_memory/core/semantic_index.rb +244 -0
- data/lib/swarm_memory/core/storage.rb +288 -0
- data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
- data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
- data/lib/swarm_memory/dsl/memory_config.rb +113 -0
- data/lib/swarm_memory/embeddings/embedder.rb +36 -0
- data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
- data/lib/swarm_memory/errors.rb +21 -0
- data/lib/swarm_memory/integration/cli_registration.rb +30 -0
- data/lib/swarm_memory/integration/configuration.rb +43 -0
- data/lib/swarm_memory/integration/registration.rb +31 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
- data/lib/swarm_memory/optimization/analyzer.rb +244 -0
- data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
- data/lib/swarm_memory/prompts/memory.md.erb +109 -0
- data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
- data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
- data/lib/swarm_memory/search/semantic_search.rb +112 -0
- data/lib/swarm_memory/search/text_search.rb +42 -0
- data/lib/swarm_memory/search/text_similarity.rb +80 -0
- data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
- data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
- data/lib/swarm_memory/tools/load_skill.rb +313 -0
- data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
- data/lib/swarm_memory/tools/memory_delete.rb +99 -0
- data/lib/swarm_memory/tools/memory_edit.rb +185 -0
- data/lib/swarm_memory/tools/memory_glob.rb +160 -0
- data/lib/swarm_memory/tools/memory_grep.rb +247 -0
- data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
- data/lib/swarm_memory/tools/memory_read.rb +123 -0
- data/lib/swarm_memory/tools/memory_write.rb +231 -0
- data/lib/swarm_memory/utils.rb +50 -0
- data/lib/swarm_memory/version.rb +5 -0
- data/lib/swarm_memory.rb +166 -0
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
- data/lib/swarm_sdk/agent/builder.rb +461 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
- data/lib/swarm_sdk/agent/chat.rb +1159 -0
- data/lib/swarm_sdk/agent/context.rb +112 -0
- data/lib/swarm_sdk/agent/context_manager.rb +309 -0
- data/lib/swarm_sdk/agent/definition.rb +556 -0
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +296 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +197 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +147 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +51 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +75 -0
- data/lib/swarm_sdk/model_aliases.json +5 -0
- data/lib/swarm_sdk/models.json +1 -0
- data/lib/swarm_sdk/models.rb +120 -0
- data/lib/swarm_sdk/node/agent_config.rb +49 -0
- data/lib/swarm_sdk/node/builder.rb +439 -0
- data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
- data/lib/swarm_sdk/node_context.rb +170 -0
- data/lib/swarm_sdk/node_orchestrator.rb +384 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/plugin.rb +147 -0
- data/lib/swarm_sdk/plugin_registry.rb +101 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
- data/lib/swarm_sdk/swarm/builder.rb +586 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
- data/lib/swarm_sdk/swarm.rb +982 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/clock.rb +44 -0
- data/lib/swarm_sdk/tools/delegate.rb +164 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +228 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +93 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
- data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/think.rb +95 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +157 -0
- data/llm.v2.txt +13407 -0
- data/rubocop/cop/security/no_reflection_methods.rb +47 -0
- data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
- data/swarm_cli.gemspec +57 -0
- data/swarm_memory.gemspec +28 -0
- data/swarm_sdk.gemspec +41 -0
- data/team.yml +1 -1
- data/team_full.yml +1875 -0
- data/{team_v2.yml → team_sdk.yml} +121 -52
- metadata +247 -4
- data/EXAMPLES.md +0 -164
|
@@ -0,0 +1,711 @@
|
|
|
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
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Called when swarm execution completes successfully
|
|
102
|
+
def on_success(result:)
|
|
103
|
+
if @mode == :non_interactive
|
|
104
|
+
# Full result display with summary
|
|
105
|
+
@output.puts
|
|
106
|
+
@output.puts @divider.full
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Print result (handles mode internally)
|
|
110
|
+
print_result(result)
|
|
111
|
+
|
|
112
|
+
# Only print summary in non-interactive mode
|
|
113
|
+
print_summary(result) if @mode == :non_interactive
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Called when swarm execution fails
|
|
117
|
+
def on_error(error:, duration: nil)
|
|
118
|
+
@output.puts
|
|
119
|
+
@output.puts @divider.full
|
|
120
|
+
print_error(error)
|
|
121
|
+
@output.puts @divider.full
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def handle_user_request(entry)
|
|
127
|
+
agent = entry[:agent]
|
|
128
|
+
@usage_tracker.track_agent(agent)
|
|
129
|
+
@usage_tracker.track_llm_request(entry[:usage])
|
|
130
|
+
|
|
131
|
+
# Stop any delegation waiting spinner (in case this agent was delegated to)
|
|
132
|
+
unless @quiet
|
|
133
|
+
delegation_spinner = "delegation_#{agent}".to_sym
|
|
134
|
+
@spinner_manager.stop(delegation_spinner) if @spinner_manager.active?(delegation_spinner)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Render agent thinking line
|
|
138
|
+
@output.puts @event_renderer.agent_thinking(
|
|
139
|
+
agent: agent,
|
|
140
|
+
model: entry[:model],
|
|
141
|
+
timestamp: entry[:timestamp],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Show tools available
|
|
145
|
+
if entry[:tools]&.any?
|
|
146
|
+
@output.puts @event_renderer.tools_available(entry[:tools], indent: @depth_tracker.get(agent))
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Show delegation options
|
|
150
|
+
if entry[:delegates_to]&.any?
|
|
151
|
+
@output.puts @event_renderer.delegates_to(
|
|
152
|
+
entry[:delegates_to],
|
|
153
|
+
indent: @depth_tracker.get(agent),
|
|
154
|
+
color_cache: @color_cache,
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
@output.puts
|
|
159
|
+
|
|
160
|
+
# Start spinner for agent thinking
|
|
161
|
+
unless @quiet
|
|
162
|
+
spinner_key = "agent_#{agent}".to_sym
|
|
163
|
+
@spinner_manager.start(spinner_key, "#{agent} is thinking...")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def handle_agent_step(entry)
|
|
168
|
+
agent = entry[:agent]
|
|
169
|
+
indent_level = @depth_tracker.get(agent)
|
|
170
|
+
|
|
171
|
+
# Stop agent thinking spinner
|
|
172
|
+
unless @quiet
|
|
173
|
+
spinner_key = "agent_#{agent}".to_sym
|
|
174
|
+
@spinner_manager.stop(spinner_key)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Track usage
|
|
178
|
+
if entry[:usage]
|
|
179
|
+
@usage_tracker.track_llm_request(entry[:usage])
|
|
180
|
+
@last_context_percentage[agent] = entry[:usage][:tokens_used_percentage]
|
|
181
|
+
|
|
182
|
+
# Render usage stats
|
|
183
|
+
@output.puts @event_renderer.usage_stats(
|
|
184
|
+
tokens: entry[:usage][:total_tokens] || 0,
|
|
185
|
+
cost: entry[:usage][:total_cost] || 0.0,
|
|
186
|
+
context_pct: entry[:usage][:tokens_used_percentage],
|
|
187
|
+
remaining: entry[:usage][:tokens_remaining],
|
|
188
|
+
cumulative: entry[:usage][:cumulative_total_tokens],
|
|
189
|
+
indent: indent_level,
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Display thinking text (if present)
|
|
194
|
+
if entry[:content] && !entry[:content].empty?
|
|
195
|
+
thinking = @event_renderer.thinking_text(entry[:content], indent: indent_level)
|
|
196
|
+
@output.puts thinking unless thinking.empty?
|
|
197
|
+
@output.puts if thinking && !thinking.empty?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Show tool request summary
|
|
201
|
+
tool_count = entry[:tool_calls]&.size || 0
|
|
202
|
+
if tool_count > 0
|
|
203
|
+
indent = @depth_tracker.indent(agent)
|
|
204
|
+
@output.puts "#{indent} #{@pastel.dim("→ Requesting #{tool_count} tool#{"s" if tool_count > 1}...")}"
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
@output.puts
|
|
208
|
+
@output.puts @divider.event(indent: indent_level)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def handle_agent_stop(entry)
|
|
212
|
+
agent = entry[:agent]
|
|
213
|
+
indent_level = @depth_tracker.get(agent)
|
|
214
|
+
|
|
215
|
+
# Stop agent thinking spinner with success
|
|
216
|
+
unless @quiet
|
|
217
|
+
spinner_key = "agent_#{agent}".to_sym
|
|
218
|
+
@spinner_manager.success(spinner_key, "completed")
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Track usage
|
|
222
|
+
if entry[:usage]
|
|
223
|
+
@usage_tracker.track_llm_request(entry[:usage])
|
|
224
|
+
@last_context_percentage[agent] = entry[:usage][:tokens_used_percentage]
|
|
225
|
+
|
|
226
|
+
# Render usage stats
|
|
227
|
+
@output.puts @event_renderer.usage_stats(
|
|
228
|
+
tokens: entry[:usage][:total_tokens] || 0,
|
|
229
|
+
cost: entry[:usage][:total_cost] || 0.0,
|
|
230
|
+
context_pct: entry[:usage][:tokens_used_percentage],
|
|
231
|
+
remaining: entry[:usage][:tokens_remaining],
|
|
232
|
+
cumulative: entry[:usage][:cumulative_total_tokens],
|
|
233
|
+
indent: indent_level,
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Display final response (only for top-level agent in non-interactive mode)
|
|
238
|
+
# In interactive mode, response is shown by print_result to avoid duplication
|
|
239
|
+
if entry[:content] && !entry[:content].empty? && indent_level.zero? && @mode == :non_interactive
|
|
240
|
+
@output.puts @event_renderer.agent_response(
|
|
241
|
+
agent: agent,
|
|
242
|
+
timestamp: entry[:timestamp],
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Render response content
|
|
246
|
+
indent = @depth_tracker.indent(agent)
|
|
247
|
+
response_lines = entry[:content].split("\n")
|
|
248
|
+
|
|
249
|
+
if @truncate && response_lines.length > 12
|
|
250
|
+
response_lines.first(12).each { |line| @output.puts "#{indent} #{line}" }
|
|
251
|
+
@output.puts "#{indent} #{@pastel.dim("... (#{response_lines.length - 12} more lines)")}"
|
|
252
|
+
else
|
|
253
|
+
response_lines.each { |line| @output.puts "#{indent} #{line}" }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
@output.puts
|
|
258
|
+
@output.puts @event_renderer.agent_completed(agent: agent)
|
|
259
|
+
@output.puts
|
|
260
|
+
@output.puts @divider.event(indent: indent_level)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def handle_tool_call(entry)
|
|
264
|
+
agent = entry[:agent]
|
|
265
|
+
@usage_tracker.track_tool_call(tool_call_id: entry[:tool_call_id], tool_name: entry[:tool])
|
|
266
|
+
|
|
267
|
+
# Special handling for Think tool - show as thoughts, not as a tool call
|
|
268
|
+
if entry[:tool] == "Think" && entry[:arguments] && entry[:arguments]["thoughts"]
|
|
269
|
+
thoughts = entry[:arguments]["thoughts"]
|
|
270
|
+
thinking = @event_renderer.thinking_text(thoughts, indent: @depth_tracker.get(agent))
|
|
271
|
+
@output.puts thinking unless thinking.empty?
|
|
272
|
+
@output.puts
|
|
273
|
+
# Don't show spinner for Think tool
|
|
274
|
+
return
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Render tool call event
|
|
278
|
+
@output.puts @event_renderer.tool_call(
|
|
279
|
+
agent: agent,
|
|
280
|
+
tool: entry[:tool],
|
|
281
|
+
timestamp: entry[:timestamp],
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Show arguments (skip TodoWrite unless verbose)
|
|
285
|
+
args = entry[:arguments]
|
|
286
|
+
show_args = args && !args.empty?
|
|
287
|
+
show_args &&= entry[:tool] != "TodoWrite" || @verbose
|
|
288
|
+
|
|
289
|
+
if show_args
|
|
290
|
+
@output.puts @event_renderer.tool_arguments(
|
|
291
|
+
args,
|
|
292
|
+
indent: @depth_tracker.get(agent),
|
|
293
|
+
truncate: @truncate,
|
|
294
|
+
)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
@output.puts
|
|
298
|
+
|
|
299
|
+
# Start spinner for tool execution
|
|
300
|
+
unless @quiet || entry[:tool] == "TodoWrite"
|
|
301
|
+
spinner_key = "tool_#{entry[:tool_call_id]}".to_sym
|
|
302
|
+
@spinner_manager.start(spinner_key, "Executing #{entry[:tool]}...")
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def handle_tool_result(entry)
|
|
307
|
+
agent = entry[:agent]
|
|
308
|
+
tool_name = entry[:tool] || @usage_tracker.tool_name_for(entry[:tool_call_id])
|
|
309
|
+
|
|
310
|
+
# Special handling for Think tool - skip showing result (already shown as thoughts)
|
|
311
|
+
if tool_name == "Think"
|
|
312
|
+
# Don't show anything - thoughts were already displayed in handle_tool_call
|
|
313
|
+
# Start spinner for agent processing
|
|
314
|
+
unless @quiet
|
|
315
|
+
spinner_key = "agent_#{agent}".to_sym
|
|
316
|
+
indent = @depth_tracker.indent(agent)
|
|
317
|
+
@spinner_manager.start(spinner_key, "#{indent}#{agent} is processing...")
|
|
318
|
+
end
|
|
319
|
+
return
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Stop tool spinner with success
|
|
323
|
+
unless @quiet || tool_name == "TodoWrite"
|
|
324
|
+
spinner_key = "tool_#{entry[:tool_call_id]}".to_sym
|
|
325
|
+
@spinner_manager.success(spinner_key, "completed")
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Special handling for TodoWrite
|
|
329
|
+
if tool_name == "TodoWrite"
|
|
330
|
+
display_todo_list(agent, entry[:timestamp])
|
|
331
|
+
else
|
|
332
|
+
@output.puts @event_renderer.tool_result(
|
|
333
|
+
agent: agent,
|
|
334
|
+
timestamp: entry[:timestamp],
|
|
335
|
+
tool: tool_name,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# Render result content
|
|
339
|
+
if entry[:result].is_a?(String) && !entry[:result].empty?
|
|
340
|
+
result_text = @event_renderer.tool_result_content(
|
|
341
|
+
entry[:result],
|
|
342
|
+
indent: @depth_tracker.get(agent),
|
|
343
|
+
truncate: !@verbose,
|
|
344
|
+
)
|
|
345
|
+
@output.puts result_text unless result_text.empty?
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
@output.puts
|
|
350
|
+
@output.puts @divider.event(indent: @depth_tracker.get(agent))
|
|
351
|
+
|
|
352
|
+
# Start spinner for agent processing tool result
|
|
353
|
+
# The agent will determine what to do next (more tools or finish)
|
|
354
|
+
# This spinner will be stopped by the next agent_step or agent_stop event
|
|
355
|
+
unless @quiet
|
|
356
|
+
spinner_key = "agent_#{agent}".to_sym
|
|
357
|
+
indent = @depth_tracker.indent(agent)
|
|
358
|
+
@spinner_manager.start(spinner_key, "#{indent}#{agent} is processing...")
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def handle_agent_delegation(entry)
|
|
363
|
+
@usage_tracker.track_tool_call
|
|
364
|
+
|
|
365
|
+
@output.puts @event_renderer.delegation(
|
|
366
|
+
from: entry[:agent],
|
|
367
|
+
to: entry[:delegate_to],
|
|
368
|
+
timestamp: entry[:timestamp],
|
|
369
|
+
)
|
|
370
|
+
@output.puts
|
|
371
|
+
|
|
372
|
+
# Show arguments if present
|
|
373
|
+
if entry[:arguments] && !entry[:arguments].empty?
|
|
374
|
+
@output.puts @event_renderer.tool_arguments(
|
|
375
|
+
entry[:arguments],
|
|
376
|
+
indent: @depth_tracker.get(entry[:agent]),
|
|
377
|
+
truncate: @truncate,
|
|
378
|
+
)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
@output.puts
|
|
382
|
+
|
|
383
|
+
# Start spinner waiting for delegated agent
|
|
384
|
+
unless @quiet
|
|
385
|
+
spinner_key = "delegation_#{entry[:delegate_to]}".to_sym
|
|
386
|
+
indent = @depth_tracker.indent(entry[:agent])
|
|
387
|
+
@spinner_manager.start(spinner_key, "#{indent}Waiting for #{entry[:delegate_to]}...")
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def handle_delegation_result(entry)
|
|
392
|
+
@output.puts @event_renderer.delegation_result(
|
|
393
|
+
from: entry[:delegate_from],
|
|
394
|
+
to: entry[:agent],
|
|
395
|
+
timestamp: entry[:timestamp],
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Render result content
|
|
399
|
+
if entry[:result].is_a?(String) && !entry[:result].empty?
|
|
400
|
+
result_text = @event_renderer.tool_result_content(
|
|
401
|
+
entry[:result],
|
|
402
|
+
indent: @depth_tracker.get(entry[:agent]),
|
|
403
|
+
truncate: !@verbose,
|
|
404
|
+
)
|
|
405
|
+
@output.puts result_text unless result_text.empty?
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
@output.puts
|
|
409
|
+
@output.puts @divider.event(indent: @depth_tracker.get(entry[:agent]))
|
|
410
|
+
|
|
411
|
+
# Start spinner for agent processing delegation result
|
|
412
|
+
unless @quiet
|
|
413
|
+
spinner_key = "agent_#{entry[:agent]}".to_sym
|
|
414
|
+
indent = @depth_tracker.indent(entry[:agent])
|
|
415
|
+
@spinner_manager.start(spinner_key, "#{indent}#{entry[:agent]} is processing...")
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def handle_context_warning(entry)
|
|
420
|
+
agent = entry[:agent]
|
|
421
|
+
threshold = entry[:threshold]
|
|
422
|
+
current_usage = entry[:current_usage]
|
|
423
|
+
tokens_remaining = entry[:tokens_remaining]
|
|
424
|
+
|
|
425
|
+
# Determine warning severity
|
|
426
|
+
type = threshold == "90%" ? :error : :warning
|
|
427
|
+
|
|
428
|
+
@output.puts @panel.render(
|
|
429
|
+
type: type,
|
|
430
|
+
title: "CONTEXT WARNING #{@agent_badge.render(agent)}",
|
|
431
|
+
lines: [
|
|
432
|
+
@pastel.public_send((type == :error ? :red : :yellow), "Context usage: #{current_usage} (threshold: #{threshold})"),
|
|
433
|
+
@pastel.dim("Tokens remaining: #{SwarmCLI::UI::Formatters::Number.format(tokens_remaining)}"),
|
|
434
|
+
],
|
|
435
|
+
indent: @depth_tracker.get(agent),
|
|
436
|
+
)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def handle_model_lookup_warning(entry)
|
|
440
|
+
agent = entry[:agent]
|
|
441
|
+
model = entry[:model]
|
|
442
|
+
error_message = entry[:error_message]
|
|
443
|
+
suggestions = entry[:suggestions] || []
|
|
444
|
+
|
|
445
|
+
lines = [
|
|
446
|
+
@pastel.yellow("Model '#{model}' not found in registry"),
|
|
447
|
+
]
|
|
448
|
+
|
|
449
|
+
if suggestions.any?
|
|
450
|
+
lines << @pastel.dim("Did you mean one of these?")
|
|
451
|
+
suggestions.each do |suggestion|
|
|
452
|
+
model_id = suggestion[:id] || suggestion["id"]
|
|
453
|
+
context = suggestion[:context_window] || suggestion["context_window"]
|
|
454
|
+
context_display = context ? " (#{SwarmCLI::UI::Formatters::Number.format(context)} tokens)" : ""
|
|
455
|
+
lines << " #{@pastel.cyan("•")} #{@pastel.white(model_id)}#{@pastel.dim(context_display)}"
|
|
456
|
+
end
|
|
457
|
+
else
|
|
458
|
+
lines << @pastel.dim("Error: #{error_message}")
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
lines << @pastel.dim("Context tracking unavailable for this model.")
|
|
462
|
+
|
|
463
|
+
@output.puts @panel.render(
|
|
464
|
+
type: :warning,
|
|
465
|
+
title: "MODEL WARNING #{@agent_badge.render(agent)}",
|
|
466
|
+
lines: lines,
|
|
467
|
+
indent: 0, # Always at root level (warnings shown at boot, not during execution)
|
|
468
|
+
)
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def handle_compression_started(entry)
|
|
472
|
+
agent = entry[:agent]
|
|
473
|
+
message_count = entry[:message_count]
|
|
474
|
+
estimated_tokens = entry[:estimated_tokens]
|
|
475
|
+
|
|
476
|
+
@output.puts @panel.render(
|
|
477
|
+
type: :info,
|
|
478
|
+
title: "CONTEXT COMPRESSION #{@agent_badge.render(agent)}",
|
|
479
|
+
lines: [
|
|
480
|
+
@pastel.dim("Compressing #{message_count} messages (~#{SwarmCLI::UI::Formatters::Number.format(estimated_tokens)} tokens)..."),
|
|
481
|
+
],
|
|
482
|
+
indent: @depth_tracker.get(agent),
|
|
483
|
+
)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def handle_compression_completed(entry)
|
|
487
|
+
agent = entry[:agent]
|
|
488
|
+
original_messages = entry[:original_message_count]
|
|
489
|
+
compressed_messages = entry[:compressed_message_count]
|
|
490
|
+
messages_removed = entry[:messages_removed]
|
|
491
|
+
original_tokens = entry[:original_tokens]
|
|
492
|
+
compressed_tokens = entry[:compressed_tokens]
|
|
493
|
+
compression_ratio = entry[:compression_ratio]
|
|
494
|
+
time_taken = entry[:time_taken]
|
|
495
|
+
|
|
496
|
+
@output.puts @panel.render(
|
|
497
|
+
type: :success,
|
|
498
|
+
title: "COMPRESSION COMPLETE #{@agent_badge.render(agent)}",
|
|
499
|
+
lines: [
|
|
500
|
+
"#{@pastel.dim("Messages:")} #{original_messages} → #{compressed_messages} #{@pastel.green("(-#{messages_removed})")}",
|
|
501
|
+
"#{@pastel.dim("Tokens:")} #{SwarmCLI::UI::Formatters::Number.format(original_tokens)} → #{SwarmCLI::UI::Formatters::Number.format(compressed_tokens)} #{@pastel.green("(#{(compression_ratio * 100).round(1)}%)")}",
|
|
502
|
+
"#{@pastel.dim("Time taken:")} #{SwarmCLI::UI::Formatters::Time.duration(time_taken)}",
|
|
503
|
+
],
|
|
504
|
+
indent: @depth_tracker.get(agent),
|
|
505
|
+
)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def handle_hook_executed(entry)
|
|
509
|
+
hook_event = entry[:hook_event]
|
|
510
|
+
agent = entry[:agent]
|
|
511
|
+
success = entry[:success]
|
|
512
|
+
blocked = entry[:blocked]
|
|
513
|
+
stderr = entry[:stderr]
|
|
514
|
+
exit_code = entry[:exit_code]
|
|
515
|
+
|
|
516
|
+
@output.puts @event_renderer.hook_executed(
|
|
517
|
+
hook_event: hook_event,
|
|
518
|
+
agent: agent,
|
|
519
|
+
timestamp: entry[:timestamp],
|
|
520
|
+
success: success,
|
|
521
|
+
blocked: blocked,
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
# Show stderr if present
|
|
525
|
+
if stderr && !stderr.empty?
|
|
526
|
+
indent = @depth_tracker.indent(agent)
|
|
527
|
+
|
|
528
|
+
if blocked && hook_event == "user_prompt"
|
|
529
|
+
@output.puts
|
|
530
|
+
@output.puts "#{indent} #{@pastel.bold.red("⛔ Prompt Blocked by Hook:")}"
|
|
531
|
+
stderr.lines.each { |line| @output.puts "#{indent} #{@pastel.red(line.chomp)}" }
|
|
532
|
+
@output.puts "#{indent} #{@pastel.dim("(Prompt was not sent to the agent)")}"
|
|
533
|
+
elsif blocked
|
|
534
|
+
@output.puts "#{indent} #{@pastel.red("Blocked:")} #{@pastel.red(stderr)}"
|
|
535
|
+
else
|
|
536
|
+
@output.puts "#{indent} #{@pastel.yellow("Message:")} #{@pastel.dim(stderr)}"
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Show exit code in verbose mode
|
|
541
|
+
if @verbose && exit_code
|
|
542
|
+
indent = @depth_tracker.indent(agent)
|
|
543
|
+
code_color = if exit_code.zero?
|
|
544
|
+
:green
|
|
545
|
+
else
|
|
546
|
+
(exit_code == 2 ? :red : :yellow)
|
|
547
|
+
end
|
|
548
|
+
@output.puts "#{indent} #{@pastel.dim("Exit code:")} #{@pastel.public_send(code_color, exit_code)}"
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
@output.puts
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def handle_breakpoint_enter(entry)
|
|
555
|
+
agent = entry[:agent]
|
|
556
|
+
event = entry[:event]
|
|
557
|
+
|
|
558
|
+
# Pause all spinners to allow clean interactive debugging
|
|
559
|
+
@spinner_manager.pause_all
|
|
560
|
+
|
|
561
|
+
# Show debugging notice
|
|
562
|
+
@output.puts
|
|
563
|
+
@output.puts @pastel.yellow("#{SwarmCLI::UI::Icons::THINKING} Breakpoint: Entering interactive debugging (#{event} hook)")
|
|
564
|
+
@output.puts @pastel.dim(" Agent: #{agent}")
|
|
565
|
+
@output.puts @pastel.dim(" Type 'exit' to continue execution")
|
|
566
|
+
@output.puts
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def handle_breakpoint_exit(entry)
|
|
570
|
+
# Resume all spinners after debugging
|
|
571
|
+
@spinner_manager.resume_all
|
|
572
|
+
|
|
573
|
+
@output.puts
|
|
574
|
+
@output.puts @pastel.green("#{SwarmCLI::UI::Icons::SUCCESS} Breakpoint: Resuming execution")
|
|
575
|
+
@output.puts
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def display_todo_list(agent, timestamp)
|
|
579
|
+
todos = SwarmSDK::Tools::Stores::TodoManager.get_todos(agent.to_sym)
|
|
580
|
+
indent = @depth_tracker.indent(agent)
|
|
581
|
+
time = SwarmCLI::UI::Formatters::Time.timestamp(timestamp)
|
|
582
|
+
|
|
583
|
+
if todos.empty?
|
|
584
|
+
@output.puts "#{indent}#{@pastel.dim(time)} #{@pastel.cyan("#{SwarmCLI::UI::Icons::BULLET} Todo list")} updated (empty)"
|
|
585
|
+
return
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
@output.puts "#{indent}#{@pastel.dim(time)} #{@pastel.cyan("#{SwarmCLI::UI::Icons::BULLET} Todo list")} updated:"
|
|
589
|
+
@output.puts
|
|
590
|
+
|
|
591
|
+
todos.each_with_index do |todo, index|
|
|
592
|
+
status = todo[:status] || todo["status"]
|
|
593
|
+
content = todo[:content] || todo["content"]
|
|
594
|
+
num = index + 1
|
|
595
|
+
|
|
596
|
+
line = case status
|
|
597
|
+
when "completed"
|
|
598
|
+
"#{indent} #{@pastel.dim("#{num}.")} #{@pastel.dim.strikethrough(content)}"
|
|
599
|
+
when "in_progress"
|
|
600
|
+
"#{indent} #{@pastel.bold.yellow("#{num}.")} #{@pastel.bold(content)}"
|
|
601
|
+
when "pending"
|
|
602
|
+
"#{indent} #{@pastel.white("#{num}.")} #{content}"
|
|
603
|
+
else
|
|
604
|
+
"#{indent} #{num}. #{content}"
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
@output.puts line
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
def print_header(swarm_name, lead_agent)
|
|
612
|
+
@output.puts
|
|
613
|
+
@output.puts @pastel.bold.bright_cyan("#{SwarmCLI::UI::Icons::SPARKLES} SwarmSDK - AI Agent Orchestration #{SwarmCLI::UI::Icons::SPARKLES}")
|
|
614
|
+
@output.puts @divider.full
|
|
615
|
+
@output.puts "#{@pastel.bold("Swarm:")} #{@pastel.cyan(swarm_name)}"
|
|
616
|
+
@output.puts "#{@pastel.bold("Lead Agent:")} #{@pastel.cyan(lead_agent)}"
|
|
617
|
+
@output.puts
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def print_prompt(prompt)
|
|
621
|
+
@output.puts @pastel.bold("#{SwarmCLI::UI::Icons::THINKING} Task Prompt:")
|
|
622
|
+
@output.puts @pastel.bright_white(prompt)
|
|
623
|
+
@output.puts
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def print_result(result)
|
|
627
|
+
return unless result.content && !result.content.empty?
|
|
628
|
+
|
|
629
|
+
# Interactive mode: Just show the response content directly
|
|
630
|
+
if @mode == :interactive
|
|
631
|
+
# Render markdown if content looks like markdown
|
|
632
|
+
content_to_display = if looks_like_markdown?(result.content)
|
|
633
|
+
begin
|
|
634
|
+
TTY::Markdown.parse(result.content)
|
|
635
|
+
rescue StandardError
|
|
636
|
+
result.content
|
|
637
|
+
end
|
|
638
|
+
else
|
|
639
|
+
result.content
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
@output.puts content_to_display
|
|
643
|
+
@output.puts
|
|
644
|
+
return
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# Non-interactive mode: Full result display with header and dividers
|
|
648
|
+
@output.puts
|
|
649
|
+
@output.puts @pastel.bold.green("#{SwarmCLI::UI::Icons::SUCCESS} Execution Complete")
|
|
650
|
+
@output.puts
|
|
651
|
+
@output.puts @pastel.bold("#{SwarmCLI::UI::Icons::RESPONSE} Final Response from #{@agent_badge.render(result.agent)}:")
|
|
652
|
+
@output.puts
|
|
653
|
+
@output.puts @divider.full
|
|
654
|
+
|
|
655
|
+
# Render markdown if content looks like markdown
|
|
656
|
+
content_to_display = if looks_like_markdown?(result.content)
|
|
657
|
+
begin
|
|
658
|
+
TTY::Markdown.parse(result.content)
|
|
659
|
+
rescue StandardError
|
|
660
|
+
result.content
|
|
661
|
+
end
|
|
662
|
+
else
|
|
663
|
+
result.content
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
@output.puts content_to_display
|
|
667
|
+
@output.puts @divider.full
|
|
668
|
+
@output.puts
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def print_summary(result)
|
|
672
|
+
@output.puts @pastel.bold("#{SwarmCLI::UI::Icons::INFO} Execution Summary:")
|
|
673
|
+
@output.puts
|
|
674
|
+
|
|
675
|
+
# Agents used (colored list)
|
|
676
|
+
agents_display = @agent_badge.render_list(@usage_tracker.agents)
|
|
677
|
+
@output.puts " #{SwarmCLI::UI::Icons::AGENT} #{@pastel.bold("Agents used:")} #{agents_display}"
|
|
678
|
+
|
|
679
|
+
# Metrics
|
|
680
|
+
@output.puts " #{SwarmCLI::UI::Icons::LLM} #{@pastel.bold("LLM Requests:")} #{result.llm_requests}"
|
|
681
|
+
@output.puts " #{SwarmCLI::UI::Icons::TOOL} #{@pastel.bold("Tool Calls:")} #{result.tool_calls_count}"
|
|
682
|
+
@output.puts " #{SwarmCLI::UI::Icons::TOKENS} #{@pastel.bold("Total Tokens:")} #{SwarmCLI::UI::Formatters::Number.format(result.total_tokens)}"
|
|
683
|
+
@output.puts " #{SwarmCLI::UI::Icons::COST} #{@pastel.bold("Total Cost:")} #{SwarmCLI::UI::Formatters::Cost.format(result.total_cost, pastel: @pastel)}"
|
|
684
|
+
@output.puts " #{SwarmCLI::UI::Icons::TIME} #{@pastel.bold("Duration:")} #{SwarmCLI::UI::Formatters::Time.duration(result.duration)}"
|
|
685
|
+
|
|
686
|
+
@output.puts
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
def print_error(error)
|
|
690
|
+
@output.puts
|
|
691
|
+
@output.puts @pastel.bold.red("#{SwarmCLI::UI::Icons::ERROR} Execution Failed")
|
|
692
|
+
@output.puts
|
|
693
|
+
@output.puts @pastel.red("Error: #{error.class.name}")
|
|
694
|
+
@output.puts @pastel.red(error.message)
|
|
695
|
+
@output.puts
|
|
696
|
+
|
|
697
|
+
return unless error.backtrace
|
|
698
|
+
|
|
699
|
+
@output.puts @pastel.dim("Backtrace:")
|
|
700
|
+
error.backtrace.first(5).each do |line|
|
|
701
|
+
@output.puts @pastel.dim(" #{line}")
|
|
702
|
+
end
|
|
703
|
+
@output.puts
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def looks_like_markdown?(text)
|
|
707
|
+
text.match?(/^#+\s|^\*\s|^-\s|^\d+\.\s|```|\[.+\]\(.+\)/)
|
|
708
|
+
end
|
|
709
|
+
end
|
|
710
|
+
end
|
|
711
|
+
end
|