Module: SwarmCLI::V3::EventRenderer

Defined in:
lib/swarm_cli/v3/event_renderer.rb

Overview

Stateless formatter for V3 agent events.

Converts event hashes (emitted by SwarmSDK::V3::EventStream) into styled terminal strings. Returns nil for unrecognized event types so the caller can silently skip them.

Examples:

event = { type: "tool_call", tool: "Read", arguments: { file_path: "foo.rb" } }
EventRenderer.format(event)
# => "  \u{1F4D6} \e[2mRead(file_path: \"foo.rb\")\e[0m"

Constant Summary collapse

TOOL_EMOJIS =
{
  "Read" => "\u{1F4D6}",
  "Write" => "\u{270F}\u{FE0F}",
  "Edit" => "\u{1F527}",
  "Bash" => "\u{1F4BB}",
  "Grep" => "\u{1F50D}",
  "Glob" => "\u{1F4C2}",
  "Think" => "\u{1F4AD}",
  "Reboot" => "\u{1F504}",
  "Clock" => "\u{1F550}",
  "SubTask" => "\u{1F41D}",
}.freeze

Class Method Summary collapse

Class Method Details

.format(event) ⇒ String?

Format an event hash into a styled terminal string.

Examples:

tool_call event

EventRenderer.format(type: "tool_call", tool: "Bash", arguments: { command: "ls" })

subtask event

EventRenderer.format(type: "subtask_spawned", title: "research", depth: 1)

Parameters:

  • event (Hash)

    event data with at least a :type key

Returns:

  • (String, nil)

    formatted string, or nil if the event type is unknown



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/swarm_cli/v3/event_renderer.rb', line 40

def format(event)
  case event[:type]
  when "tool_call"
    emoji = TOOL_EMOJIS[event[:tool]] || "\u{1F527}"
    args = format_tool_args(event[:arguments])
    "  #{emoji} #{ANSIColors.dim("#{event[:tool]}(#{args})")}"
  when "tool_result"
    format_tool_result(event[:result_preview])
  when "subtask_spawned"
    ANSIColors.magenta("  \u{1F41D} SubTask spawned: #{event[:title]} (depth: #{event[:depth]})")
  when "subtask_completed"
    ANSIColors.green("  \u{2705} SubTask completed: #{event[:title]}")
  when "subtask_failed"
    ANSIColors.red("  \u{274C} SubTask failed: #{event[:title]} \u{2014} #{event[:error]}")
  when "memory_retrieval", "memory_wait_ingestion", "memory_eviction"
    label = event[:type].sub("memory_", "").tr("_", " ")
    ANSIColors.dim("  \u{1F9E0} #{label} (#{event[:elapsed_ms]}ms)")
  when "tool_skipped"
    ANSIColors.yellow("  \u{23ED}\u{FE0F} #{event[:tool]} skipped (steering)")
  when "steering_injected"
    ANSIColors.yellow("  \u{1F3AF} Steering injected (#{event[:message_count]} message#{"s" if event[:message_count] != 1})")
  when "follow_up_injected"
    ANSIColors.cyan("  \u{1F4CB} Follow-up processing (#{event[:message_count]} message#{"s" if event[:message_count] != 1})")
  when "memory_defrag_start"
    ANSIColors.cyan("  \u{1F9F9} Starting defrag (#{event[:total_cards]} cards, #{event[:total_clusters]} clusters)")
  when "memory_defrag_complete"
    ANSIColors.green("  \u{2705} Defrag complete in #{event[:elapsed_ms]}ms")
  when "loop_started"
    ANSIColors.cyan("  \u{1F504} Starting refinement (max #{event[:max_iterations]} iterations)")
  when "loop_iteration_completed"
    delta = event[:delta_score] ? " (similarity: #{(event[:delta_score] * 100).round(1)}%)" : ""
    ANSIColors.dim("  \u{1F504} Iteration #{event[:iteration]} complete#{delta}")
  when "loop_completed"
    label = event[:converged] ? "converged" : "#{event[:iterations]} iterations"
    ANSIColors.green("  \u{2705} Refinement complete (#{label})")
  end
end

.format_activity(event) ⇒ String?

Format a memory event as a short activity status string.

Used by the activity indicator to show memory operations inline on the “Working…” line rather than as separate output lines.

Parameters:

  • event (Hash)

    memory event with :type and :elapsed_ms

Returns:

  • (String, nil)

    status string or nil if not a memory event



96
97
98
99
100
101
102
# File 'lib/swarm_cli/v3/event_renderer.rb', line 96

def format_activity(event)
  case event[:type]
  when "memory_retrieval", "memory_wait_ingestion", "memory_eviction"
    label = event[:type].sub("memory_", "").tr("_", " ")
    "\u{1F9E0} #{label}: #{event[:elapsed_ms]}ms"
  end
end

.format_defrag_progress(event) ⇒ String?

Format a defrag progress event for activity indicator display.

Parameters:

  • event (Hash)

    defrag progress event

Returns:

  • (String, nil)

    progress string or nil



82
83
84
85
86
87
# File 'lib/swarm_cli/v3/event_renderer.rb', line 82

def format_defrag_progress(event)
  return unless event[:type] == "memory_defrag_progress"

  pct = (event[:overall_current].to_f / event[:overall_total] * 100).round
  "\u{1F9F9} [#{pct}%] #{event[:description]}"
end

.format_tool_args(args) ⇒ String

Format tool arguments as a compact key: value string.

Parameters:

  • args (Hash, nil)

    tool arguments

Returns:

  • (String)

    formatted arguments



157
158
159
160
161
162
163
164
165
# File 'lib/swarm_cli/v3/event_renderer.rb', line 157

def format_tool_args(args)
  return "" if args.nil? || args.empty?

  pairs = args.map do |k, v|
    val = v.is_a?(String) ? "\"#{truncate(v, 50)}\"" : truncate(v.inspect, 50)
    "#{k}: #{val}"
  end
  truncate(pairs.join(", "), 120)
end

.format_tool_result(text, max_lines: 3, max_line_length: 100) ⇒ String

Format tool result preview with multiline support.

Shows up to 3 lines, truncates long lines, and indicates when content is truncated. Each line is indented and dimmed.

Parameters:

  • text (String, nil)

    tool result text

  • max_lines (Integer) (defaults to: 3)

    maximum lines to show (default: 3)

  • max_line_length (Integer) (defaults to: 100)

    maximum length per line (default: 100)

Returns:

  • (String)

    formatted multiline string



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/swarm_cli/v3/event_renderer.rb', line 113

def format_tool_result(text, max_lines: 3, max_line_length: 100)
  return ANSIColors.dim("  \u{21B3} (empty)") if text.nil? || text.to_s.strip.empty?

  lines = text.to_s.lines.map(&:chomp)
  truncated_vertically = lines.size > max_lines

  # Take first max_lines
  display_lines = lines.first(max_lines)

  # Truncate each line horizontally
  formatted = display_lines.map do |line|
    truncated_line = if line.length > max_line_length
      "#{line[0...max_line_length]}\u{2026}"
    else
      line
    end
    ANSIColors.dim("  \u{21B3} #{truncated_line}")
  end

  # Add ellipsis indicator if truncated vertically
  if truncated_vertically
    remaining = lines.size - max_lines
    formatted << ANSIColors.dim("  \u{21B3} \u{2026} (#{remaining} more line#{"s" if remaining != 1})")
  end

  formatted.join("\n")
end

.truncate(text, max) ⇒ String

Truncate text to a maximum length, replacing newlines with return arrows.

Parameters:

  • text (String, nil)

    text to truncate

  • max (Integer)

    maximum length

Returns:

  • (String)

    truncated text (may include a trailing ellipsis)



146
147
148
149
150
151
# File 'lib/swarm_cli/v3/event_renderer.rb', line 146

def truncate(text, max)
  return "" if text.nil?

  text = text.to_s.tr("\n", "\u{21B5}")
  text.length > max ? "#{text[0...max]}\u{2026}" : text
end