Class: Rubino::UI::AgentMenu
- Inherits:
-
Object
- Object
- Rubino::UI::AgentMenu
- Defined in:
- lib/rubino/ui/agent_menu.rb
Overview
Live subagent picker hosted by the bottom composer. It only chooses an entry; the /agents command remains the single owner of drill-in semantics.
Constant Summary collapse
- MAX_ROWS =
5- MAIN_ROW =
The synthetic "return to the main session" row, shown at the BOTTOM of the picker so the list is a switcher: pick a subagent to attach/switch to it, or pick "main" to leave an attached agent (a no-op at the main prompt). A tiny struct so it answers #id like a real row (the refresh re-selection finds it by id). Identity-compared via .main_row?.
Struct.new(:id).new("__main__").freeze
Class Method Summary collapse
Instance Method Summary collapse
- #accept ⇒ Object
- #close! ⇒ Object
- #down ⇒ Object
-
#initialize(entries: -> { Tools::BackgroundTasks.instance.running }, pastel: Pastel.new) ⇒ AgentMenu
constructor
A new instance of AgentMenu.
-
#menu_items ⇒ Object
The picker rows: the live subagents, then the "◂ main" row at the bottom.
- #open! ⇒ Object
- #open? ⇒ Boolean
- #refresh! ⇒ Object
-
#rows(cols) ⇒ Object
The rendered picker rows, or [] when closed.
- #selected ⇒ Object
-
#single_live ⇒ Object
The single live subagent, or nil when there are zero or several.
-
#up! ⇒ Object
Move the highlight up one.
Constructor Details
#initialize(entries: -> { Tools::BackgroundTasks.instance.running }, pastel: Pastel.new) ⇒ AgentMenu
Returns a new instance of AgentMenu.
21 22 23 24 25 |
# File 'lib/rubino/ui/agent_menu.rb', line 21 def initialize(entries: -> { Tools::BackgroundTasks.instance.running }, pastel: Pastel.new) @entries = entries @pastel = pastel @state = nil end |
Class Method Details
.main_row?(entry) ⇒ Boolean
19 |
# File 'lib/rubino/ui/agent_menu.rb', line 19 def self.main_row?(entry) = entry.equal?(MAIN_ROW) |
Instance Method Details
#accept ⇒ Object
98 99 100 101 102 |
# File 'lib/rubino/ui/agent_menu.rb', line 98 def accept entry = selected close! entry end |
#close! ⇒ Object
62 63 64 |
# File 'lib/rubino/ui/agent_menu.rb', line 62 def close! @state = nil end |
#down ⇒ Object
84 85 86 87 88 89 90 |
# File 'lib/rubino/ui/agent_menu.rb', line 84 def down return open! unless open? @state[:selected] = [@state[:selected] + 1, @state[:items].size - 1].min sync_top true end |
#menu_items ⇒ Object
The picker rows: the live subagents, then the "◂ main" row at the bottom. Empty (so the picker stays closed) when no subagent is live — there is nothing to switch between and "main" alone would be a pointless prompt.
47 48 49 50 51 52 |
# File 'lib/rubino/ui/agent_menu.rb', line 47 def live = live_entries return [] if live.empty? live + [MAIN_ROW] end |
#open! ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/rubino/ui/agent_menu.rb', line 31 def open! # Idempotent: re-opening an ALREADY-open menu must NOT rebuild items and # reset the selection to the top — a background list mutation (a shell # finishing) that re-triggered open! while the user was on row N then # snapped the highlight to row 0, so Enter attached to the wrong row. return @state if open? items = return if items.empty? @state = { items: items, selected: 0, top: 0 } end |
#open? ⇒ Boolean
27 28 29 |
# File 'lib/rubino/ui/agent_menu.rb', line 27 def open? !@state.nil? end |
#refresh! ⇒ Object
104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/rubino/ui/agent_menu.rb', line 104 def refresh! return unless open? previous = selected&.id items = if items.empty? close! return end selected = items.index { |entry| entry.id == previous } || 0 @state = { items: items, selected: selected, top: window_top(selected, items.size) } end |
#rows(cols) ⇒ Object
The rendered picker rows, or [] when closed. Delegates the look — the
┄ background ┄ header, the scroll-window slice, the cyan ❯ + inverse
highlight, the dim rest, and the overflow footer — to the shared
MenuView, so this picker and the / command palette render alike
(#562). This menu still owns its rows: the status-coloured
id · subagent · status label and the ◂ main row.
124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/rubino/ui/agent_menu.rb', line 124 def rows(cols) return [] unless open? refresh! return [] unless open? descriptors = @state[:items].map { |entry| descriptor(entry) } MenuView.render(descriptors, cols, window: { selected: @state[:selected], top: @state[:top], max_rows: MAX_ROWS }, header: "background", hints: "Enter attaches · ← back") end |
#selected ⇒ Object
92 93 94 95 96 |
# File 'lib/rubino/ui/agent_menu.rb', line 92 def selected return unless open? @state[:items][@state[:selected]] end |
#single_live ⇒ Object
The single live subagent, or nil when there are zero or several. Lets the composer honor the "Enter to view" hint with a true one-press attach when there is nothing to choose between (#42).
57 58 59 60 |
# File 'lib/rubino/ui/agent_menu.rb', line 57 def single_live live = live_entries live.size == 1 ? live.first : nil end |
#up! ⇒ Object
Move the highlight up one. ↑ off the TOP of the list EXITS the picker:
the menu closes itself (it owns its own lifecycle) so focus returns to the
input — no stranded ❯ marker. Returns true while it stayed open and moved,
false when it closed (or was already closed), so the caller can just
up!; redraw without re-implementing the focus hand-off.
71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/rubino/ui/agent_menu.rb', line 71 def up! # rubocop:disable Naming/PredicateMethod -- a bang mutator that also reports whether it stayed open, not a pure query return false unless open? if @state[:selected].zero? close! return false end @state[:selected] -= 1 sync_top true end |