Class: Agentilda::UI::Line
- Inherits:
-
Object
- Object
- Agentilda::UI::Line
- Defined in:
- lib/agentilda/ui.rb
Overview
One item's spinner line, and the same news written to the log.
These are two readers of one story and used to be told it separately: the spinner got the phrase, the log got a start and a finish, and an animated run wrote nothing about what any agent was actually doing. A Transcript::Progress arrives here several times a second; the spinner is redrawn every time, and the log takes a line only when the phrase itself changes, which is a few dozen times an agent.
Instance Method Summary collapse
-
#alive ⇒ String
The same, as the log and the report write it.
- #call(update) ⇒ void
- #done ⇒ void
- #failed(reason) ⇒ void
-
#identity ⇒ String
What sits between the agent's name and its activity: the
claudeprocess and the round,[36123, round 01], so a line on the screen can be matched to a process inpsand a row in the report. -
#initialize(fields: {}, spinner: nil, mark: "", timeout: nil) ⇒ Line
constructor
A new instance of Line.
- #note(message) ⇒ void
-
#remaining ⇒ Integer?
What remains on this agent's clock, or nil when it has none.
-
#seconds ⇒ Float
Seconds this agent has been alive.
- #start ⇒ void
- #stop_ticker ⇒ void
-
#tick ⇒ void
The countdown, redrawn once a second on its own thread.
-
#to_proc ⇒ Proc
So a caller can pass this straight on as a block: Executor yields to it from the thread it reads the agent's stream on.
Constructor Details
#initialize(fields: {}, spinner: nil, mark: "", timeout: nil) ⇒ Line
Returns a new instance of Line.
65 66 67 68 69 70 71 72 73 74 |
# File 'lib/agentilda/ui.rb', line 65 def initialize(fields: {}, spinner: nil, mark: "", timeout: nil) @fields = fields @spinner = spinner @mark = mark @timeout = timeout @started = UI.monotonic @phrase = nil @pid = nil @ticker = nil end |
Instance Method Details
#alive ⇒ String
Returns the same, as the log and the report write it.
80 |
# File 'lib/agentilda/ui.rb', line 80 def alive = "#{seconds.round}s" |
#call(update) ⇒ void
This method returns an undefined value.
163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/agentilda/ui.rb', line 163 def call(update) if update.respond_to?(:pid) && update.pid && update.pid != @pid @pid = update.pid @spinner&.update(pid: identity) note("claude is pid #{@pid}") end @spinner&.update(meter: UI.meter(update), activity: UI.said(update.activity)) return if update.activity.nil? || update.activity == @phrase @phrase = update.activity note(update.activity) end |
#done ⇒ void
This method returns an undefined value.
147 148 149 150 151 |
# File 'lib/agentilda/ui.rb', line 147 def done stop_ticker @spinner&.success(@mark) note("finished after #{alive}") end |
#failed(reason) ⇒ void
This method returns an undefined value.
155 156 157 158 159 |
# File 'lib/agentilda/ui.rb', line 155 def failed(reason) stop_ticker @spinner&.error(UI.paint(reason, :red)) note("failed after #{alive}: #{reason}") end |
#identity ⇒ String
What sits between the agent's name and its activity: the claude
process and the round, [36123, round 01], so a line on the screen
can be matched to a process in ps and a row in the report. The pid
is unknowable until the child has been spawned and found, so it reads
… until then; a caller with neither fact gets nothing at all.
138 139 140 141 142 143 144 |
# File 'lib/agentilda/ui.rb', line 138 def identity round = @fields[:round] return "" if round.nil? && @pid.nil? inner = [(@pid || "…").to_s, ("round #{round}" if round)].compact.join(", ") UI.paint("[#{inner}]", :bright_black) end |
#note(message) ⇒ void
This method returns an undefined value.
84 |
# File 'lib/agentilda/ui.rb', line 84 def note() = UI.log(, **@fields, seconds:) |
#remaining ⇒ Integer?
What remains on this agent's clock, or nil when it has none. Floored
at zero: the executor's kill and this thread's wake-up race by up to
a second, and a line reading -0:01 accuses the wrong party.
98 99 100 101 102 |
# File 'lib/agentilda/ui.rb', line 98 def remaining return nil unless @timeout [@timeout - seconds, 0].max.round end |
#seconds ⇒ Float
Returns seconds this agent has been alive.
77 |
# File 'lib/agentilda/ui.rb', line 77 def seconds = UI.monotonic - @started |
#start ⇒ void
This method returns an undefined value.
87 88 89 90 91 |
# File 'lib/agentilda/ui.rb', line 87 def start @spinner&.update(pid: identity) tick note("started") end |
#stop_ticker ⇒ void
This method returns an undefined value.
126 127 128 129 |
# File 'lib/agentilda/ui.rb', line 126 def stop_ticker @ticker&.kill @ticker = nil end |
#tick ⇒ void
This method returns an undefined value.
The countdown, redrawn once a second on its own thread. Progress updates cannot drive it — they arrive only while the agent is talking, and a stalled agent is exactly when the clock matters most.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/agentilda/ui.rb', line 109 def tick return unless @spinner && @timeout @spinner.update(timer: UI.countdown(remaining)) @ticker ||= Thread.new do loop do sleep(1) left = remaining @spinner.update(timer: UI.countdown(left)) break unless left.positive? end rescue # A dying spinner must not take the round down with it. end end |
#to_proc ⇒ Proc
So a caller can pass this straight on as a block: Executor yields to it from the thread it reads the agent's stream on.
180 |
# File 'lib/agentilda/ui.rb', line 180 def to_proc = method(:call).to_proc |