swarm_cli 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/swarm_cli/commands/run.rb +2 -2
- data/lib/swarm_cli/config_loader.rb +11 -11
- data/lib/swarm_cli/formatters/human_formatter.rb +70 -0
- data/lib/swarm_cli/interactive_repl.rb +11 -5
- data/lib/swarm_cli/ui/icons.rb +0 -23
- data/lib/swarm_cli/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e122c7785e1681e7a88b131e9c62529c9a79eee0e99b1e7c83d0c510683eef35
|
|
4
|
+
data.tar.gz: '018d08703ab5b57a15945d7a39d26345b737587257cd88fbe0d240deca92b93d'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d6c446656f75086772b6e59532057825c2ea6ee377d25335c32c757e0dcc7ac721acbf9a19ab2d31d386b63ec16a9746e819e64d424290f67957a79641238750
|
|
7
|
+
data.tar.gz: 8c4ffbfdba471398906de81bbba52ddb439f7a774145425e4bc555bdf8f0a6ad7c557d117181ecef140be45001c0057241c9e235fd3a2bc58055d5f407efb693
|
|
@@ -134,8 +134,8 @@ module SwarmCLI
|
|
|
134
134
|
|
|
135
135
|
def emit_validation_warnings(swarm, formatter)
|
|
136
136
|
# Setup temporary logging to capture and emit warnings
|
|
137
|
-
SwarmSDK::LogCollector.
|
|
138
|
-
formatter.on_log(log_entry)
|
|
137
|
+
SwarmSDK::LogCollector.subscribe(filter: { type: "model_lookup_warning" }) do |log_entry|
|
|
138
|
+
formatter.on_log(log_entry)
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
SwarmSDK::LogStream.emitter = SwarmSDK::LogCollector
|
|
@@ -5,7 +5,7 @@ module SwarmCLI
|
|
|
5
5
|
#
|
|
6
6
|
# Supports:
|
|
7
7
|
# - YAML files (.yml, .yaml) - loaded via SwarmSDK.load_file
|
|
8
|
-
# - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm or SwarmSDK::
|
|
8
|
+
# - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm or SwarmSDK::Workflow instance
|
|
9
9
|
#
|
|
10
10
|
# @example Load YAML config
|
|
11
11
|
# swarm = ConfigLoader.load("config.yml")
|
|
@@ -19,10 +19,10 @@ module SwarmCLI
|
|
|
19
19
|
#
|
|
20
20
|
# Detects file type by extension:
|
|
21
21
|
# - .yml, .yaml -> Load as YAML using SwarmSDK.load_file
|
|
22
|
-
# - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm or SwarmSDK::
|
|
22
|
+
# - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm or SwarmSDK::Workflow instance
|
|
23
23
|
#
|
|
24
24
|
# @param path [String, Pathname] Path to configuration file
|
|
25
|
-
# @return [SwarmSDK::Swarm, SwarmSDK::
|
|
25
|
+
# @return [SwarmSDK::Swarm, SwarmSDK::Workflow] Configured swarm or workflow instance
|
|
26
26
|
# @raise [SwarmCLI::ConfigurationError] If file not found or invalid format
|
|
27
27
|
def load(path)
|
|
28
28
|
path = Pathname.new(path).expand_path
|
|
@@ -59,27 +59,27 @@ module SwarmCLI
|
|
|
59
59
|
# Load Ruby DSL configuration file
|
|
60
60
|
#
|
|
61
61
|
# Executes the Ruby file in a clean binding and expects it to return
|
|
62
|
-
# a SwarmSDK::Swarm or SwarmSDK::
|
|
63
|
-
# use SwarmSDK.build or create a Swarm/
|
|
62
|
+
# a SwarmSDK::Swarm or SwarmSDK::Workflow instance. The file should
|
|
63
|
+
# use SwarmSDK.build or SwarmSDK.workflow or create a Swarm/Workflow instance directly.
|
|
64
64
|
#
|
|
65
65
|
# @param path [Pathname] Path to Ruby DSL file
|
|
66
|
-
# @return [SwarmSDK::Swarm, SwarmSDK::
|
|
66
|
+
# @return [SwarmSDK::Swarm, SwarmSDK::Workflow] Configured swarm or workflow instance
|
|
67
67
|
# @raise [ConfigurationError] If file doesn't return a valid instance
|
|
68
68
|
def load_ruby_dsl(path)
|
|
69
69
|
# Read the file content
|
|
70
70
|
content = path.read
|
|
71
71
|
|
|
72
72
|
# Execute in a clean binding with SwarmSDK available
|
|
73
|
-
# This allows the DSL file to use SwarmSDK.build directly
|
|
73
|
+
# This allows the DSL file to use SwarmSDK.build or SwarmSDK.workflow directly
|
|
74
74
|
result = eval(content, binding, path.to_s, 1) # rubocop:disable Security/Eval
|
|
75
75
|
|
|
76
|
-
# Validate result is a Swarm or
|
|
76
|
+
# Validate result is a Swarm or Workflow instance
|
|
77
77
|
# Both have the same execute(prompt) interface
|
|
78
|
-
unless result.is_a?(SwarmSDK::Swarm) || result.is_a?(SwarmSDK::
|
|
78
|
+
unless result.is_a?(SwarmSDK::Swarm) || result.is_a?(SwarmSDK::Workflow)
|
|
79
79
|
raise ConfigurationError,
|
|
80
|
-
"Ruby DSL file must return a SwarmSDK::Swarm or SwarmSDK::
|
|
80
|
+
"Ruby DSL file must return a SwarmSDK::Swarm or SwarmSDK::Workflow instance. " \
|
|
81
81
|
"Got: #{result.class}. " \
|
|
82
|
-
"Use: SwarmSDK.build { ... } or
|
|
82
|
+
"Use: SwarmSDK.build { ... } or SwarmSDK.workflow { ... }"
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
result
|
|
@@ -95,11 +95,18 @@ module SwarmCLI
|
|
|
95
95
|
handle_breakpoint_enter(entry)
|
|
96
96
|
when "breakpoint_exit"
|
|
97
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)
|
|
98
102
|
end
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
# Called when swarm execution completes successfully
|
|
102
106
|
def on_success(result:)
|
|
107
|
+
# Defensive: ensure all spinners are stopped before showing result
|
|
108
|
+
@spinner_manager.stop_all
|
|
109
|
+
|
|
103
110
|
if @mode == :non_interactive
|
|
104
111
|
# Full result display with summary
|
|
105
112
|
@output.puts
|
|
@@ -115,6 +122,9 @@ module SwarmCLI
|
|
|
115
122
|
|
|
116
123
|
# Called when swarm execution fails
|
|
117
124
|
def on_error(error:, duration: nil)
|
|
125
|
+
# Defensive: ensure all spinners are stopped before showing error
|
|
126
|
+
@spinner_manager.stop_all
|
|
127
|
+
|
|
118
128
|
@output.puts
|
|
119
129
|
@output.puts @divider.full
|
|
120
130
|
print_error(error)
|
|
@@ -575,6 +585,66 @@ module SwarmCLI
|
|
|
575
585
|
@output.puts
|
|
576
586
|
end
|
|
577
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
|
+
|
|
578
648
|
def display_todo_list(agent, timestamp)
|
|
579
649
|
todos = SwarmSDK::Tools::Stores::TodoManager.get_todos(agent.to_sym)
|
|
580
650
|
indent = @depth_tracker.indent(agent)
|
|
@@ -81,6 +81,9 @@ module SwarmCLI
|
|
|
81
81
|
display_session_summary
|
|
82
82
|
exit(130)
|
|
83
83
|
ensure
|
|
84
|
+
# Defensive: ensure all spinners are stopped on exit
|
|
85
|
+
@formatter&.spinner_manager&.stop_all
|
|
86
|
+
|
|
84
87
|
# Save history on exit
|
|
85
88
|
save_persistent_history
|
|
86
89
|
end
|
|
@@ -432,11 +435,12 @@ module SwarmCLI
|
|
|
432
435
|
end
|
|
433
436
|
end
|
|
434
437
|
|
|
438
|
+
# CRITICAL: Stop all spinners after execution completes
|
|
439
|
+
# This ensures spinner doesn't interfere with error/success display or REPL prompt
|
|
440
|
+
@formatter.spinner_manager.stop_all
|
|
441
|
+
|
|
435
442
|
# Handle cancellation (result is nil when cancelled)
|
|
436
443
|
if result.nil?
|
|
437
|
-
# Stop all active spinners
|
|
438
|
-
@formatter.spinner_manager.stop_all
|
|
439
|
-
|
|
440
444
|
puts ""
|
|
441
445
|
puts @colors[:warning].call("✗ Request cancelled by user")
|
|
442
446
|
puts ""
|
|
@@ -459,6 +463,8 @@ module SwarmCLI
|
|
|
459
463
|
# Add response to history
|
|
460
464
|
@conversation_history << { role: "agent", content: result.content }
|
|
461
465
|
rescue StandardError => e
|
|
466
|
+
# Defensive: ensure spinners are stopped on exception
|
|
467
|
+
@formatter.spinner_manager.stop_all
|
|
462
468
|
@formatter.on_error(error: e)
|
|
463
469
|
end
|
|
464
470
|
|
|
@@ -528,7 +534,7 @@ module SwarmCLI
|
|
|
528
534
|
lead = @swarm.agent(@swarm.lead_agent)
|
|
529
535
|
|
|
530
536
|
# Clear the agent's conversation history
|
|
531
|
-
lead.
|
|
537
|
+
lead.replace_messages([])
|
|
532
538
|
|
|
533
539
|
# Clear REPL conversation history
|
|
534
540
|
@conversation_history.clear
|
|
@@ -569,7 +575,7 @@ module SwarmCLI
|
|
|
569
575
|
case tool_name
|
|
570
576
|
when /^Memory/, "LoadSkill"
|
|
571
577
|
memory_tools << tool_name
|
|
572
|
-
when /^
|
|
578
|
+
when /^WorkWith/
|
|
573
579
|
delegation_tools << tool_name
|
|
574
580
|
when /^mcp__/
|
|
575
581
|
mcp_tools << tool_name
|
data/lib/swarm_cli/ui/icons.rb
CHANGED
|
@@ -31,29 +31,6 @@ module SwarmCLI
|
|
|
31
31
|
ARROW_RIGHT = "→"
|
|
32
32
|
BULLET = "•"
|
|
33
33
|
COMPRESS = "🗜️"
|
|
34
|
-
|
|
35
|
-
# All icons as hash for backward compatibility
|
|
36
|
-
ALL = {
|
|
37
|
-
thinking: THINKING,
|
|
38
|
-
response: RESPONSE,
|
|
39
|
-
success: SUCCESS,
|
|
40
|
-
error: ERROR,
|
|
41
|
-
info: INFO,
|
|
42
|
-
warning: WARNING,
|
|
43
|
-
agent: AGENT,
|
|
44
|
-
tool: TOOL,
|
|
45
|
-
delegate: DELEGATE,
|
|
46
|
-
result: RESULT,
|
|
47
|
-
hook: HOOK,
|
|
48
|
-
llm: LLM,
|
|
49
|
-
tokens: TOKENS,
|
|
50
|
-
cost: COST,
|
|
51
|
-
time: TIME,
|
|
52
|
-
sparkles: SPARKLES,
|
|
53
|
-
arrow_right: ARROW_RIGHT,
|
|
54
|
-
bullet: BULLET,
|
|
55
|
-
compress: COMPRESS,
|
|
56
|
-
}.freeze
|
|
57
34
|
end
|
|
58
35
|
end
|
|
59
36
|
end
|
data/lib/swarm_cli/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: swarm_cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paulo Arruda
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '2.
|
|
46
|
+
version: '2.2'
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '2.
|
|
53
|
+
version: '2.2'
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: tty-box
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|