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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce573683afe4e8fbb1d144984c0b476db2dac070f490a62b0b1e3b7b32a3622e
4
- data.tar.gz: e4b3a5f950a7c007ed543f00d55603b17fad3db31748a4fdce6348de680764e7
3
+ metadata.gz: e122c7785e1681e7a88b131e9c62529c9a79eee0e99b1e7c83d0c510683eef35
4
+ data.tar.gz: '018d08703ab5b57a15945d7a39d26345b737587257cd88fbe0d240deca92b93d'
5
5
  SHA512:
6
- metadata.gz: 53f75f25ffe8d8a531db038274fc1d5853a56271215eacded92dba259737f3cc2f3d61e9dd9cd20b8c8273884a7156638e6646b58fccd44ece644637a362ba16
7
- data.tar.gz: 9298ef6cda98c97db7ff9fea9c5f2176a2285dbcc74a24ef729f43ac2cca04a803f3a9d38aed68f8900c45c95f2697c24038e96909362c25e0b932ebaab3d5eb
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.on_log do |log_entry|
138
- formatter.on_log(log_entry) if log_entry[:type] == "model_lookup_warning"
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::NodeOrchestrator instance
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::NodeOrchestrator instance
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::NodeOrchestrator] Configured swarm or orchestrator instance
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::NodeOrchestrator instance. The file should
63
- # use SwarmSDK.build or create a Swarm/NodeOrchestrator instance directly.
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::NodeOrchestrator] Configured swarm or orchestrator instance
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 NodeOrchestrator instance
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::NodeOrchestrator)
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::NodeOrchestrator instance. " \
80
+ "Ruby DSL file must return a SwarmSDK::Swarm or SwarmSDK::Workflow instance. " \
81
81
  "Got: #{result.class}. " \
82
- "Use: SwarmSDK.build { ... } or Swarm.new(...)"
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.reset_messages!
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 /^DelegateTaskTo/
578
+ when /^WorkWith/
573
579
  delegation_tools << tool_name
574
580
  when /^mcp__/
575
581
  mcp_tools << tool_name
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmCLI
4
- VERSION = "2.1.2"
4
+ VERSION = "2.1.4"
5
5
  end
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.2
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.1'
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.1'
53
+ version: '2.2'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: tty-box
56
56
  requirement: !ruby/object:Gem::Requirement