Class: Rubino::UI::SubagentCards
- Inherits:
-
Object
- Object
- Rubino::UI::SubagentCards
- Defined in:
- lib/rubino/ui/subagent_cards.rb
Overview
Formats BackgroundTasks registry entries into the COLLAPSED LIVE CARDS the parent shows while one or more background subagents run (Variant A of the orchestration-UX blueprint). This is the single source of card text: the live region (UI::CLI#set_subagent_cards → BottomComposer) renders it while a turn runs, and the /agents drill-in reuses the same formatter for the expanded view. Pure formatting — it never touches the registry mutex itself (callers pass a snapshot) and writes nothing; the renderer decides where the lines go.
Collapsed card (one row per running subagent, updates in place):
▸ sa_9ae4 · explore · running · 14 tools · 38s · grep "def authenticate"
plus a single shared hint line under the block.
An entry parked on a human approval shows the approval prominently instead:
● sa_9ae4 · explore · needs approval · shell rm -rf build
Up to MAX_CARDS cards stack; a longer list collapses the overflow into a “+N more” tail so the live region stays bounded (and the single-row clamp in the composer never has to host an unbounded block).
Constant Summary collapse
- MAX_CARDS =
Cap the live block so it never grows past the registry’s own MAX_CONCURRENT (3) live children — but defend against a stale/over-long list anyway with an explicit overflow tail.
Tools::BackgroundTasks::MAX_CONCURRENT
- COLLAPSED =
Collapsed glyph (a running card) / approval glyph (needs the human) / BLOCKED glyph (an escalated ask_parent waiting on the human — RESERVED for “the tree is blocked on you” and nothing else, the distinct-signal rule).
"▸"- APPROVAL =
"●"- BLOCKED =
"⛔"
Instance Method Summary collapse
-
#approval_card_line(entry) ⇒ Object
A card for a child parked on a human approval — the approval is the most important thing on the row, so it leads (amber ●) with the command.
-
#blocked_card_line(entry) ⇒ Object
A card for a child parked on an escalated ask_parent — the ⛔ “tree is blocked on YOU” row, the loudest state.
-
#card_line(entry) ⇒ Object
One collapsed card row for a single entry.
-
#card_lines(entries) ⇒ Object
Renders the live CARD BLOCK for the running (or approval-pending) children in
entriesas an array of ready-to-print lines. -
#initialize(pastel: Pastel.new) ⇒ SubagentCards
constructor
A new instance of SubagentCards.
Constructor Details
#initialize(pastel: Pastel.new) ⇒ SubagentCards
Returns a new instance of SubagentCards.
39 40 41 |
# File 'lib/rubino/ui/subagent_cards.rb', line 39 def initialize(pastel: Pastel.new) @pastel = pastel end |
Instance Method Details
#approval_card_line(entry) ⇒ Object
A card for a child parked on a human approval — the approval is the most important thing on the row, so it leads (amber ●) with the command.
91 92 93 94 95 96 97 98 |
# File 'lib/rubino/ui/subagent_cards.rb', line 91 def approval_card_line(entry) glyph = @pastel.yellow(APPROVAL) command = entry.approval_command.to_s command = entry.approval_question.to_s if command.empty? " #{glyph} #{entry.id} · #{entry.subagent} · " + @pastel.yellow("needs approval") + ": #{first_line(command, 60)} " \ "· /agents #{entry.id}" end |
#blocked_card_line(entry) ⇒ Object
A card for a child parked on an escalated ask_parent — the ⛔ “tree is blocked on YOU” row, the loudest state. Leads with the red ⛔ glyph and the question, and points at /reply <id> (the answer verb), distinct from the approval row’s /agents <id>.
81 82 83 84 85 86 87 |
# File 'lib/rubino/ui/subagent_cards.rb', line 81 def blocked_card_line(entry) glyph = @pastel.red(BLOCKED) question = entry.ask_question.to_s " #{glyph} #{entry.id} · #{entry.subagent} · " + @pastel.red("waiting on you") + ": #{first_line(question, 60)} " \ "· /reply #{entry.id}" end |
#card_line(entry) ⇒ Object
One collapsed card row for a single entry.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/rubino/ui/subagent_cards.rb', line 61 def card_line(entry) if entry.status == :blocked_on_human blocked_card_line(entry) elsif entry.status == :needs_approval approval_card_line(entry) else glyph = @pastel.cyan(COLLAPSED) state = entry.status == :stopping ? "stopping" : "running" count = entry.tool_count.to_i body = "#{entry.id} · #{entry.subagent} · #{state} · " \ "#{count} tool#{"s" if count != 1} · #{elapsed(entry)}" body += " · #{entry.last_activity}" unless entry.last_activity.to_s.empty? " #{glyph} #{body}" end end |
#card_lines(entries) ⇒ Object
Renders the live CARD BLOCK for the running (or approval-pending) children in entries as an array of ready-to-print lines. Returns [] when nothing is live, so the renderer can clear the region. entries is a snapshot (BackgroundTasks#running) taken under the registry mutex by the caller — this method only reads the plain struct fields.
48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/rubino/ui/subagent_cards.rb', line 48 def card_lines(entries) live = entries.select { |e| live?(e) } return [] if live.empty? shown = live.first(MAX_CARDS) overflow = live.size - shown.size lines = shown.map { |e| card_line(e) } lines << @pastel.dim(" + #{overflow} more · /agents") if overflow.positive? lines << hint_line(shown) lines end |