Class: Rubino::Tools::ShellEntryAdapter

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/tools/shell_entry_adapter.rb

Overview

Read-time adapter that makes a ShellRegistry::Entry look like a BackgroundTasks::Entry to the SHARED UI seams (cards, picker, /stop, attach) WITHOUT copying its state into a second registry. This is the DRY core of "a background shell gets the same dev UX as a subagent": BackgroundTasks#running merges these adapters in, so the cards/picker render them with ZERO branches, and #stop / #feed_input route control to the live ShellRegistry process.

Why an adapter and not a real BackgroundTasks entry: a duplicated entry would need status sync (two sources of truth), would double the completion notice ShellRegistry already pushes, and would consume the subagent concurrency cap

  • allocate a dead steer_queue. Reading the live ShellRegistry entry at render time dissolves all three.

The renderers read only plain accessors (id/subagent/status/prompt/...); method_missing returns nil for any field a shell has no analogue for (approval_*, budget_request, runner, steer_queue, …) so a renderer touching one never raises.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(shell_entry, registry: ShellRegistry.instance) ⇒ ShellEntryAdapter

Returns a new instance of ShellEntryAdapter.



23
24
25
26
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 23

def initialize(shell_entry, registry: ShellRegistry.instance)
  @shell = shell_entry
  @registry = registry
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(_name, *_args) ⇒ Object



94
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 94

def method_missing(_name, *_args) = nil

Instance Attribute Details

#shellObject (readonly)

Returns the value of attribute shell.



28
29
30
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 28

def shell
  @shell
end

Instance Method Details

#activity_logObject



39
40
41
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 39

def activity_log = []
# Mirrors BackgroundTasks::Entry#budget_request — a FIELD the renderers read,
# not a predicate, so it keeps the field name (no `?`).

#budget_requestObject

Mirrors BackgroundTasks::Entry#budget_request — a FIELD the renderers read, not a predicate, so it keeps the field name (no ?).



42
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 42

def budget_request = false # rubocop:disable Naming/PredicateMethod

#depthObject

rubocop:disable Naming/PredicateMethod



43
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 43

def depth       = 0

#feed_input(text, enter: true) ⇒ Object

Attach/focus input: the user's keystrokes/line go straight to the PTY (or pipe) stdin — the shell analogue of steering a subagent.



63
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 63

def feed_input(text, enter: true) = @registry.write_input(@shell, text, enter: enter)

#idObject



30
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 30

def id          = @shell.id

#live?Boolean

True while the process is still alive — the single liveness rule every UI surface filters by (mirrors BackgroundTasks.live_status?). Routes through the registry's #running? oracle so a server that backgrounded its leader (npm run dev &) stays visible instead of vanishing the instant bash exits.

Returns:

  • (Boolean)


54
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 54

def live? = @registry.running?(@shell)

#messagesObject

The attach view replays a subagent's transcript via #messages; a shell has none — it falls through to the live output-tail render instead.



48
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 48

def messages = []

#output_allObject



88
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 88

def output_all = @registry.read_all(@shell)

#output_newObject

The live output a shell's attach view tails (no session/transcript).



87
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 87

def output_new = @registry.read_new(@shell)

#peek(_question = nil) ⇒ Object

Polymorphic counterpart to a subagent's #peek: a shell has no model context to side-infer over, so a "probe" is an instant snapshot of its recent output — NO LLM round-trip (the question is informational).



75
76
77
78
79
80
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 75

def peek(_question = nil)
  out = output_all.to_s
  return "(no output captured yet)" if out.strip.empty?

  out.lines.last(20).join.rstrip
end

#peek_hintObject

A shell's peek IS its output — no "empty context" caveat applies (the subagent's #peek_hint does).



84
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 84

def peek_hint = nil

#promptObject

the card's title line



33
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 33

def prompt      = @shell.command

#respond_to_missing?(_name, _include_private = false) ⇒ Boolean

Any field a shell has no analogue for (approval_gate, runner, steer_queue, approval_question, last_activity, finished_at, …) reads as nil so the shared renderers never raise on a shell row.

Returns:

  • (Boolean)


93
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 93

def respond_to_missing?(_name, _include_private = false) = true

#shell?Boolean

Returns:

  • (Boolean)


44
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 44

def shell?      = true

#started_atObject



34
35
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 34

def started_at  = @shell.started_at
# :running / :completed / :failed (derived)

#statusObject

:running / :completed / :failed (derived)



36
37
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 36

def status      = @registry.status(@shell)
# a shell runs no tools — nil omits the card's "N tools" segment entirely

#steer(text) ⇒ Object

Polymorphic counterpart to a subagent's #steer: a shell has no turn to fold a note into — "steering" it means writing the text to its stdin.



67
68
69
70
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 67

def steer(text) # rubocop:disable Naming/PredicateMethod -- an action mirroring Entry#steer, not a predicate
  feed_input(text)
  true
end

#stopObject

Stop from the UI (/stop / picker): SIGTERM→grace→SIGKILL the process group, then retire so the captured output stays retrievable. Reuses the one ShellRegistry kill seam (also used by shell_kill).



59
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 59

def stop = @registry.terminate(@shell)

#subagentObject



31
32
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 31

def subagent    = "shell"
# the card's title line

#tool_countObject

a shell runs no tools — nil omits the card's "N tools" segment entirely



38
# File 'lib/rubino/tools/shell_entry_adapter.rb', line 38

def tool_count = nil