Class: Mistri::Child
- Inherits:
-
Object
- Object
- Mistri::Child
- Defined in:
- lib/mistri/child.rb
Overview
A sub-agent as its parent (or the host UI) sees it: a window onto the child's own session. Everything here is derived from the store, so it reads the same from any process, while the child runs and forever after.
session.children # => [#<Mistri::Child Magpie done>, ...]
child.status # => :running, :done, :stopped, :failed
child.report # the terminal entry's report, once finished
child.transcript(tail: 20) # recent entries, image bytes stripped
child.say("Also check their pricing page")
Status is a walk over the child's own entries: a terminal entry wins; a started child is :running while its lease holds and :interrupted once it lapses (with a lock adapter; without one there is no liveness signal and no-terminal stays :running); a dispatched-but-never-started child is :queued, honestly, because the host's queue owns that gap.
Constant Summary collapse
- TERMINAL =
"subagent_result"- DISPATCHED =
"subagent_dispatched"- STARTED =
"subagent_started"- LIVE =
The states a worker can still be caught in: steerable, stoppable, worth waiting on.
%i[running queued].freeze
Instance Attribute Summary collapse
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#session_id ⇒ Object
readonly
Returns the value of attribute session_id.
Class Method Summary collapse
Instance Method Summary collapse
-
#error ⇒ Object
The terminal entry's error string, once failed.
-
#finished? ⇒ Boolean
A terminal entry exists: the child ended as done, stopped, or failed and will never run again.
-
#initialize(name:, session_id:, store:) ⇒ Child
constructor
A new instance of Child.
- #inspect ⇒ Object
- #report ⇒ Object
-
#say(text) ⇒ Object
Queue a message the child folds at its next turn boundary.
- #status ⇒ Object
-
#stop ⇒ Object
Ask a live child to stop, from any process.
- #to_h ⇒ Object
-
#transcript(tail: 20) ⇒ Object
Recent entries, oldest first, with inline image bytes replaced by a marker: transcripts are for reading and re-sending, not for hauling screenshots back into a context window.
Constructor Details
#initialize(name:, session_id:, store:) ⇒ Child
Returns a new instance of Child.
33 34 35 36 37 |
# File 'lib/mistri/child.rb', line 33 def initialize(name:, session_id:, store:) @name = name @session_id = session_id @store = store end |
Instance Attribute Details
#name ⇒ Object (readonly)
Returns the value of attribute name.
27 28 29 |
# File 'lib/mistri/child.rb', line 27 def name @name end |
#session_id ⇒ Object (readonly)
Returns the value of attribute session_id.
27 28 29 |
# File 'lib/mistri/child.rb', line 27 def session_id @session_id end |
Class Method Details
.lease_key(session_id) ⇒ Object
29 |
# File 'lib/mistri/child.rb', line 29 def self.lease_key(session_id) = "child:#{session_id}" |
.stop_key(session_id) ⇒ Object
31 |
# File 'lib/mistri/child.rb', line 31 def self.stop_key(session_id) = "child-stop:#{session_id}" |
.strip_images(value) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/mistri/child.rb', line 107 def self.strip_images(value) case value when Hash if value["data"] && value["mime_type"] value.except("data").merge("omitted" => true) else value.transform_values { |nested| strip_images(nested) } end when Array then value.map { |nested| strip_images(nested) } else value end end |
Instance Method Details
#error ⇒ Object
The terminal entry's error string, once failed.
65 66 67 |
# File 'lib/mistri/child.rb', line 65 def error terminal_entry&.fetch("error", nil) end |
#finished? ⇒ Boolean
A terminal entry exists: the child ended as done, stopped, or failed and will never run again. The question a queue retry asks; its inverse (started but no terminal) is what makes a crashed child re-runnable.
56 57 58 |
# File 'lib/mistri/child.rb', line 56 def finished? !terminal_entry.nil? end |
#inspect ⇒ Object
103 104 105 |
# File 'lib/mistri/child.rb', line 103 def inspect "#<Mistri::Child #{name} #{status}>" end |
#report ⇒ Object
60 61 62 |
# File 'lib/mistri/child.rb', line 60 def report terminal_entry&.fetch("report", nil) end |
#say(text) ⇒ Object
Queue a message the child folds at its next turn boundary. Delivery is honest, not instant: a child mid-step sees it after that step, and a child that finishes first never sees it.
81 82 83 |
# File 'lib/mistri/child.rb', line 81 def say(text) session.steer(text) end |
#status ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/mistri/child.rb', line 39 def status log = session.entries terminal = log.reverse_each.find { |entry| entry["type"] == TERMINAL } return terminal["status"].to_sym if terminal if log.any? { |entry| entry["type"] == DISPATCHED } && log.none? { |entry| entry["type"] == STARTED } return :queued end return :interrupted if Mistri.locks && !Mistri.locks.held?(self.class.lease_key(@session_id)) :running end |
#stop ⇒ Object
Ask a live child to stop, from any process. A running child's runner sees the flag within a tick and trips its own signal; a queued child is cancelled outright with a stopped terminal, which the runner honors by never starting it. Stop is cross-process by nature, so it needs a lock adapter; without one this returns false. An action that reports acceptance, not a predicate.
91 92 93 94 95 96 97 |
# File 'lib/mistri/child.rb', line 91 def stop # rubocop:disable Naming/PredicateMethod return false unless Mistri.locks session.append(TERMINAL, "status" => "stopped") if status == :queued Mistri.locks.set_flag(self.class.stop_key(@session_id)) true end |
#to_h ⇒ Object
99 100 101 |
# File 'lib/mistri/child.rb', line 99 def to_h { "name" => name, "session_id" => session_id, "status" => status.to_s } end |
#transcript(tail: 20) ⇒ Object
Recent entries, oldest first, with inline image bytes replaced by a marker: transcripts are for reading and re-sending, not for hauling screenshots back into a context window.
72 73 74 75 76 |
# File 'lib/mistri/child.rb', line 72 def transcript(tail: 20) entries = session.entries entries = entries.last(tail) if tail entries.map { |entry| self.class.strip_images(entry) } end |