pub struct RelayProducer { /* private fields */ }Expand description
One supervised agent process’s browser, as the daemon sees it.
A producer is per PROCESS (per agent WS connection), not per conversation:
car do --serve builds one AssistantRuntime and multiplexes every chat
session through it, so the process has exactly one browser. It may
therefore back several views — one per conversation key it registered —
and every push fans out to all of them.
Implementations§
Source§impl RelayProducer
impl RelayProducer
pub fn new( client_id: String, agent_id: String, channel: Arc<WsChannel>, ) -> Arc<Self> ⓘ
pub fn client_id(&self) -> &str
pub fn agent_id(&self) -> &str
pub fn is_alive(&self) -> bool
Sourcepub async fn presentation(&self) -> WirePresentation
pub async fn presentation(&self) -> WirePresentation
The cached presentation — what browser.view.subscribe snapshots and
what require_control reads.
Sourcepub async fn set_signin_attention(
&self,
attention: Option<Arc<dyn SignInAttention>>,
conversation_id: Option<String>,
)
pub async fn set_signin_attention( &self, attention: Option<Arc<dyn SignInAttention>>, conversation_id: Option<String>, )
Install (or move) the one process-level attention route.
A wait stays pinned to the conversation that raised it. Another turn registering while the process is blocked must not move the badge to that unrelated chat; its route becomes eligible only after resolution.
Sourcepub async fn detach_signin_attention(&self, conversation_id: Option<&str>)
pub async fn detach_signin_attention(&self, conversation_id: Option<&str>)
Resolve the route conversation_id owns, and drop the sink only if
this view was the LAST one this producer serves.
Three things are being separated here.
Retiring an older view after the route already moved to a newer one must not clear the newer badge — that is the key check at the top.
One supervised process backs up to MAX_VIEWS_PER_PRODUCER views,
so nulling attention because the view that happened to own the route
retired left the producer with no sink while it was still serving the
others: a sign-in raised on a surviving view then fell straight through
Self::decide_signin_transition’s attention guard and told nobody.
When a survivor exists the route MOVES instead — to the newest one,
because that is the turn the operator is actually looking at. views
is in registration order (see Self::views_past_the_cap), so the
newest is the LAST match; taking the first handed the banner and the
badge to the oldest surviving turn and opened a stale conversation.
And the wait itself does not end just because the view reporting it
retired. The resolve below is true of the ROUTE, not of the browser,
so when the route moves the wait is immediately re-raised against its
new owner — otherwise announced sits at None against a
pending_signin that is still Some, and since a browser parked on a
static login page publishes no further presentation, nothing would
re-announce it. The operator sees the badge move rather than vanish.
pub async fn signin_snapshot(&self) -> Option<BrowserSignInSnapshot>
Sourcepub async fn control_status(&self) -> ControlStatus
pub async fn control_status(&self) -> ControlStatus
Who is driving, derived from the cache. No round trip, by design: this is consulted on every input call.
Sourcepub async fn control(
&self,
control: ViewControl,
) -> Result<(ControlOwner, Vec<ControlEffect>), String>
pub async fn control( &self, control: ViewControl, ) -> Result<(ControlOwner, Vec<ControlEffect>), String>
Drive the reducer that lives WITH the browser, in the agent process, and bring its effects back — the daemon owns the grace-period clock.
Fallible, and the caller must respect that. A transition that never
reached the process did not happen: the process’s reducer still says
whatever it said before. Reporting success would leave the two sides
disagreeing about who is driving — the daemon admitting input the
process then refuses, and, worse, a person told the blackout is up when
the process never entered it.
Returns the owner the agent’s reducer landed on, taken from THIS
response rather than from the cache: the presentation pump writes that
cache too, unordered against this call, so a re-read can answer with a
pre-transition snapshot (see BrowserView::take_control).
Sourcepub async fn input(&self, input: ViewInput) -> Result<Option<String>, String>
pub async fn input(&self, input: ViewInput) -> Result<Option<String>, String>
Relay one user input to the process, which executes it against its own
BrowserTools exactly as the in-daemon path does. Errors come back
verbatim — the same code produces them on both sides.
Sourcepub fn desired_capture(&self) -> bool
pub fn desired_capture(&self) -> bool
The capture state this producer currently wants, for the registration ack to carry.
The daemon’s signal is per-producer and edge-published while the agent
process’s capture watch is per-PROCESS and survives the connection —
so a process that was capturing when its session dropped
(note_disconnected’s send(false) never reaches the wire; the pump
returns at !is_alive()) reconnects under a NEW producer whose count is
0 and whose send_if_modified emits nothing. It kept screencasting and
pushing JPEGs with nobody watching. Answering it on the ack costs no
extra round trip and resynchronises exactly when the process reappears.
Sourcepub fn start_capture(&self)
pub fn start_capture(&self)
A view started streaming. The first one turns capture on in the process.
Sourcepub fn stop_capture(&self)
pub fn stop_capture(&self)
A view stopped streaming. The last one turns capture off.
Sourcepub async fn attach_view(&self, view: &Arc<BrowserView>)
pub async fn attach_view(&self, view: &Arc<BrowserView>)
Bind a view to this producer so pushes reach it.
Sourcepub async fn views_past_the_cap(&self) -> Vec<Arc<BrowserView>>
pub async fn views_past_the_cap(&self) -> Vec<Arc<BrowserView>>
The views this producer backs beyond the newest
MAX_VIEWS_PER_PRODUCER, oldest first — the ones a fresh
registration retires. views is in registration order, which is what
makes “oldest” answerable here at all: the daemon sees only opaque
conversation ids and cannot tell one turn’s key from another
conversation’s.
Sourcepub async fn set_presentation(&self, presentation: WirePresentation)
pub async fn set_presentation(&self, presentation: WirePresentation)
Seed the cache before the first push — the register call carries the process’s current presentation so a view is never born empty when its browser is not.
Sourcepub async fn push_presentation(&self, presentation: WirePresentation)
pub async fn push_presentation(&self, presentation: WirePresentation)
The process pushed a presentation delta. Cache it, then let every view re-read: the view’s own dedup decides whether that is an event.
Sourcepub async fn push_frame(&self, frame: WireFrame)
pub async fn push_frame(&self, frame: WireFrame)
The process pushed a screencast frame.
Only views somebody is actually WATCHING get one, and the last of them
gets the frame by move. A WireFrame is a base64 full-viewport JPEG, so
each clone is a memcpy of a few hundred KB — and this fanned out to
every view the producer had ever registered, watched or not. The
ordinary case (one drawer, on one conversation) is now zero clones.
A skipped view’s cursor does not advance, which is exactly right:
subscribe hands out the CURRENT cursor, so a drawer arriving later
starts from wherever the view is and detects gaps from there.
Sourcepub async fn push_host_connected(&self, connected: bool)
pub async fn push_host_connected(&self, connected: bool)
Tell this process that a host-client connected or disconnected.
A reverse call like the other three agent.browser.* methods, so it
travels the same path and the process answers the same way. Its result
is dropped: the daemon has nothing to do about a process that cannot
be told, and the caller — a disconnect sweep or an auth handshake —
must not wait on it. ProducerRegistry::broadcast_host_connected is
what keeps that non-blocking.
Sourcepub async fn note_disconnected(&self)
pub async fn note_disconnected(&self)
The process’s connection dropped. Every call from here on is a clean error, and the views report an empty browser — which is the truth: the process is gone and its Chromium went with it.
The views stay REGISTERED. When the supervisor restarts the process and
it registers the same conversation again, that registration replaces
this view through the ordinary adopt path, so a drawer that never
unsubscribed follows the agent to its new process without the cursor
moving backwards.
Auto Trait Implementations§
impl !Freeze for RelayProducer
impl !RefUnwindSafe for RelayProducer
impl !UnwindSafe for RelayProducer
impl Send for RelayProducer
impl Sync for RelayProducer
impl Unpin for RelayProducer
impl UnsafeUnpin for RelayProducer
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more