Class: Rubino::UI::Null
- Inherits:
-
Base
- Object
- Base
- Rubino::UI::Null
show all
- Defined in:
- lib/rubino/ui/null.rb
Overview
Null UI adapter that discards all output. Used in testing and background job execution where no terminal output is needed.
Instance Attribute Summary collapse
Instance Method Summary
collapse
-
#approval_blocked? ⇒ Boolean
-
#ask(_prompt) ⇒ Object
-
#assistant_text(text) ⇒ Object
-
#blank_line ⇒ Object
-
#blocked_messages ⇒ Object
The single-line block notices captured during a headless run, in order, so the one-shot CLI can echo them to stderr before exiting non-zero (#260) — UI::Null otherwise swallows every #warning into @messages.
-
#body(text) ⇒ Object
-
#box_close(*pieces, color: nil) ⇒ Object
-
#box_open(*pieces, at: nil, color: nil) ⇒ Object
-
#branch_confirmation(new_id:, parent_id:, title:, included_probe:) ⇒ Object
-
#compression_finished(metadata, at: nil) ⇒ Object
-
#compression_started(at: nil) ⇒ Object
-
#confirm(_question, scope: nil, **_context) ⇒ Object
Headless: there is no human to ask, so FAIL CLOSED (#260).
-
#confirm_destructive(_question) ⇒ Object
Destructive confirm (#218): no human to ask, so fail closed (decline) — never destroy on the non-interactive Null adapter.
-
#error(message) ⇒ Object
-
#info(message) ⇒ Object
-
#initialize ⇒ Null
constructor
-
#input_injected(text) ⇒ Object
-
#interactive? ⇒ Boolean
No interactive session — no terminal, no approval gate.
-
#job_enqueued(type) ⇒ Object
-
#job_finished(type) ⇒ Object
-
#job_started(type) ⇒ Object
-
#mode_changed(name, previous: nil) ⇒ Object
-
#note(text) ⇒ Object
-
#probe_aside(answer) ⇒ Object
-
#queued(text) ⇒ Object
-
#reasoning_changed(mode, previous: nil) ⇒ Object
-
#reasoning_status(mode) ⇒ Object
-
#replay_user_input(text, at: nil) ⇒ Object
-
#reset! ⇒ Object
Resets captured messages (useful between test cases).
-
#select(_prompt, _choices) ⇒ Object
No interactive selection off a real terminal; callers fall back to a non-interactive path (e.g. the static /sessions table + shortcut).
-
#separator ⇒ Object
-
#status(message) ⇒ Object
-
#stream(chunk) ⇒ Object
-
#stream_end ⇒ Object
-
#subagent_approval_choice ⇒ Object
The unified arrow-key subagent approval (TUI-6) has no terminal to draw on headless: return nil (“no decision”), which the /agents handler reads as “re-prompt / leave parked” — never an auto-approve or auto-deny.
-
#success(message) ⇒ Object
-
#table(headers:, rows:) ⇒ Object
-
#think_changed(effort, previous: nil) ⇒ Object
-
#think_status(effort) ⇒ Object
-
#thinking_finished ⇒ Object
-
#thinking_started ⇒ Object
-
#tool_blocked(message) ⇒ Object
Latched by ToolExecutor when a tool is blocked for approval in this headless run (#260).
-
#tool_body(text, kind: :plain) ⇒ Object
-
#tool_chunk(name, chunk, kind: :plain) ⇒ Object
-
#tool_finished(name, result: nil) ⇒ Object
-
#tool_started(name, arguments: nil, at: nil) ⇒ Object
-
#warning(message) ⇒ Object
Methods inherited from Base
#blocking_human_input?, #hint_row, #panel_line, #turn_interrupted
Constructor Details
#initialize ⇒ Null
Returns a new instance of Null.
11
12
13
|
# File 'lib/rubino/ui/null.rb', line 11
def initialize
@messages = []
end
|
Instance Attribute Details
#messages ⇒ Object
Returns the value of attribute messages.
9
10
11
|
# File 'lib/rubino/ui/null.rb', line 9
def messages
@messages
end
|
Instance Method Details
#approval_blocked? ⇒ Boolean
147
148
149
|
# File 'lib/rubino/ui/null.rb', line 147
def approval_blocked?
@approval_blocked == true
end
|
#ask(_prompt) ⇒ Object
95
96
97
|
# File 'lib/rubino/ui/null.rb', line 95
def ask(_prompt)
nil
end
|
#assistant_text(text) ⇒ Object
47
48
49
|
# File 'lib/rubino/ui/null.rb', line 47
def assistant_text(text)
@messages << { level: :assistant_text, message: text }
end
|
#blank_line ⇒ Object
204
205
206
|
# File 'lib/rubino/ui/null.rb', line 204
def blank_line
@messages << { level: :blank_line, message: "" }
end
|
#blocked_messages ⇒ Object
The single-line block notices captured during a headless run, in order, so the one-shot CLI can echo them to stderr before exiting non-zero (#260) — UI::Null otherwise swallows every #warning into @messages.
154
155
156
|
# File 'lib/rubino/ui/null.rb', line 154
def blocked_messages
@messages.select { |m| m[:level] == :tool_blocked }.map { |m| m[:message] }
end
|
#body(text) ⇒ Object
43
44
45
|
# File 'lib/rubino/ui/null.rb', line 43
def body(text)
@messages << { level: :body, message: text }
end
|
#box_close(*pieces, color: nil) ⇒ Object
39
40
41
|
# File 'lib/rubino/ui/null.rb', line 39
def box_close(*pieces, color: nil)
@messages << { level: :box_close, pieces: pieces, color: color }
end
|
#box_open(*pieces, at: nil, color: nil) ⇒ Object
35
36
37
|
# File 'lib/rubino/ui/null.rb', line 35
def box_open(*pieces, at: nil, color: nil)
@messages << { level: :box_open, pieces: pieces, at: at, color: color }
end
|
#branch_confirmation(new_id:, parent_id:, title:, included_probe:) ⇒ Object
59
60
61
62
63
64
65
|
# File 'lib/rubino/ui/null.rb', line 59
def branch_confirmation(new_id:, parent_id:, title:, included_probe:)
@messages << {
level: :branch_confirmation,
message: { new_id: new_id, parent_id: parent_id, title: title,
included_probe: included_probe }
}
end
|
#compression_finished(metadata, at: nil) ⇒ Object
184
185
186
|
# File 'lib/rubino/ui/null.rb', line 184
def compression_finished(metadata, at: nil)
@messages << { level: :compression_finished, message: metadata, at: at }
end
|
#compression_started(at: nil) ⇒ Object
180
181
182
|
# File 'lib/rubino/ui/null.rb', line 180
def compression_started(at: nil)
@messages << { level: :compression_started, message: "", at: at }
end
|
#confirm(_question, scope: nil, **_context) ⇒ Object
Headless: there is no human to ask, so FAIL CLOSED (#260). The Null adapter drives the one-shot / scripted ‘rubino prompt` / `-q` path; it used to return true here, silently auto-approving every write and every non-allowlisted shell command — a prompt-injection→RCE foot-gun (the Gemini-CLI / Dec-2025 auto-approve-writes pattern). ToolExecutor now checks #interactive? BEFORE ever reaching #confirm, so this is the belt-and-suspenders floor: declining is the only safe default off a TTY. `scope:` is part of the shared UI contract (ToolExecutor always passes it); the Null adapter ignores it.
121
122
123
|
# File 'lib/rubino/ui/null.rb', line 121
def confirm(_question, scope: nil, **_context)
false
end
|
#confirm_destructive(_question) ⇒ Object
Destructive confirm (#218): no human to ask, so fail closed (decline) — never destroy on the non-interactive Null adapter.
160
161
162
|
# File 'lib/rubino/ui/null.rb', line 160
def confirm_destructive(_question)
false
end
|
#error(message) ⇒ Object
27
28
29
|
# File 'lib/rubino/ui/null.rb', line 27
def error(message)
@messages << { level: :error, message: message }
end
|
#info(message) ⇒ Object
15
16
17
|
# File 'lib/rubino/ui/null.rb', line 15
def info(message)
@messages << { level: :info, message: message }
end
|
232
233
234
|
# File 'lib/rubino/ui/null.rb', line 232
def input_injected(text)
@messages << { level: :input_injected, message: text }
end
|
#interactive? ⇒ Boolean
No interactive session — no terminal, no approval gate. Tells ToolExecutor to fail closed on any tool that needs approval (#260).
127
128
129
|
# File 'lib/rubino/ui/null.rb', line 127
def interactive?
false
end
|
#job_enqueued(type) ⇒ Object
188
189
190
|
# File 'lib/rubino/ui/null.rb', line 188
def job_enqueued(type)
@messages << { level: :job_enqueued, message: type }
end
|
#job_finished(type) ⇒ Object
196
197
198
|
# File 'lib/rubino/ui/null.rb', line 196
def job_finished(type)
@messages << { level: :job_finished, message: type }
end
|
#job_started(type) ⇒ Object
192
193
194
|
# File 'lib/rubino/ui/null.rb', line 192
def job_started(type)
@messages << { level: :job_started, message: type }
end
|
#mode_changed(name, previous: nil) ⇒ Object
208
209
210
|
# File 'lib/rubino/ui/null.rb', line 208
def mode_changed(name, previous: nil)
@messages << { level: :mode_changed, message: name, previous: previous }
end
|
#note(text) ⇒ Object
51
52
53
|
# File 'lib/rubino/ui/null.rb', line 51
def note(text)
@messages << { level: :note, message: text }
end
|
#probe_aside(answer) ⇒ Object
55
56
57
|
# File 'lib/rubino/ui/null.rb', line 55
def probe_aside(answer)
@messages << { level: :probe_aside, message: answer.to_s }
end
|
#queued(text) ⇒ Object
228
229
230
|
# File 'lib/rubino/ui/null.rb', line 228
def queued(text)
@messages << { level: :queued, message: text }
end
|
#reasoning_changed(mode, previous: nil) ⇒ Object
216
217
218
|
# File 'lib/rubino/ui/null.rb', line 216
def reasoning_changed(mode, previous: nil)
@messages << { level: :reasoning_changed, message: mode, previous: previous }
end
|
#reasoning_status(mode) ⇒ Object
212
213
214
|
# File 'lib/rubino/ui/null.rb', line 212
def reasoning_status(mode)
@messages << { level: :reasoning_status, message: mode }
end
|
79
80
81
|
# File 'lib/rubino/ui/null.rb', line 79
def replay_user_input(text, at: nil)
@messages << { level: :replay_user_input, message: text, at: at }
end
|
#reset! ⇒ Object
Resets captured messages (useful between test cases)
237
238
239
|
# File 'lib/rubino/ui/null.rb', line 237
def reset!
@messages = []
end
|
#select(_prompt, _choices) ⇒ Object
No interactive selection off a real terminal; callers fall back to a non-interactive path (e.g. the static /sessions table + shortcut).
101
102
103
|
# File 'lib/rubino/ui/null.rb', line 101
def select(_prompt, _choices)
nil
end
|
#separator ⇒ Object
200
201
202
|
# File 'lib/rubino/ui/null.rb', line 200
def separator
@messages << { level: :separator, message: "" }
end
|
#status(message) ⇒ Object
31
32
33
|
# File 'lib/rubino/ui/null.rb', line 31
def status(message)
@messages << { level: :status, message: message }
end
|
#stream(chunk) ⇒ Object
67
68
69
70
71
72
73
|
# File 'lib/rubino/ui/null.rb', line 67
def stream(chunk)
text = chunk[:text].to_s
type = chunk[:type] || :content
@messages << { level: :stream, message: text, stream_type: type }
end
|
#stream_end ⇒ Object
75
76
77
|
# File 'lib/rubino/ui/null.rb', line 75
def stream_end
@messages << { level: :stream_end, message: "" }
end
|
#subagent_approval_choice ⇒ Object
The unified arrow-key subagent approval (TUI-6) has no terminal to draw on headless: return nil (“no decision”), which the /agents handler reads as “re-prompt / leave parked” — never an auto-approve or auto-deny.
108
109
110
|
# File 'lib/rubino/ui/null.rb', line 108
def subagent_approval_choice
nil
end
|
#success(message) ⇒ Object
19
20
21
|
# File 'lib/rubino/ui/null.rb', line 19
def success(message)
@messages << { level: :success, message: message }
end
|
#table(headers:, rows:) ⇒ Object
91
92
93
|
# File 'lib/rubino/ui/null.rb', line 91
def table(headers:, rows:)
@messages << { level: :table, message: { headers: , rows: rows } }
end
|
#think_changed(effort, previous: nil) ⇒ Object
224
225
226
|
# File 'lib/rubino/ui/null.rb', line 224
def think_changed(effort, previous: nil)
@messages << { level: :think_changed, message: effort, previous: previous }
end
|
#think_status(effort) ⇒ Object
220
221
222
|
# File 'lib/rubino/ui/null.rb', line 220
def think_status(effort)
@messages << { level: :think_status, message: effort }
end
|
#thinking_finished ⇒ Object
87
88
89
|
# File 'lib/rubino/ui/null.rb', line 87
def thinking_finished
@messages << { level: :thinking_finished, message: "" }
end
|
#thinking_started ⇒ Object
83
84
85
|
# File 'lib/rubino/ui/null.rb', line 83
def thinking_started
@messages << { level: :thinking_started, message: "" }
end
|
Latched by ToolExecutor when a tool is blocked for approval in this headless run (#260). The one-shot CLI reads #approval_blocked? after the run to exit NON-ZERO so CI/automation fails loudly.
134
135
136
137
138
139
140
141
142
143
144
145
|
# File 'lib/rubino/ui/null.rb', line 134
def tool_blocked(message)
@approval_blocked = true
@messages << { level: :tool_blocked, message: message }
Rubino::Output::HeadlessBlockLatch.record(message) if Rubino.headless?
end
|
#tool_body(text, kind: :plain) ⇒ Object
172
173
174
|
# File 'lib/rubino/ui/null.rb', line 172
def tool_body(text, kind: :plain)
@messages << { level: :tool_body, message: text, kind: kind }
end
|
176
177
178
|
# File 'lib/rubino/ui/null.rb', line 176
def tool_chunk(name, chunk, kind: :plain)
@messages << { level: :tool_chunk, name: name, chunk: chunk, kind: kind }
end
|
168
169
170
|
# File 'lib/rubino/ui/null.rb', line 168
def tool_finished(name, result: nil)
@messages << { level: :tool_finished, message: name }
end
|
164
165
166
|
# File 'lib/rubino/ui/null.rb', line 164
def tool_started(name, arguments: nil, at: nil)
@messages << { level: :tool_started, message: name, arguments: arguments, at: at }
end
|
#warning(message) ⇒ Object
23
24
25
|
# File 'lib/rubino/ui/null.rb', line 23
def warning(message)
@messages << { level: :warning, message: message }
end
|