Skip to main content

RelayProducer

Struct RelayProducer 

Source
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

Source

pub fn new( client_id: String, agent_id: String, channel: Arc<WsChannel>, ) -> Arc<Self>

Source

pub fn client_id(&self) -> &str

Source

pub fn agent_id(&self) -> &str

Source

pub fn is_alive(&self) -> bool

Source

pub async fn presentation(&self) -> WirePresentation

The cached presentation — what browser.view.subscribe snapshots and what require_control reads.

Source

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.

Source

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.

Source

pub async fn signin_snapshot(&self) -> Option<BrowserSignInSnapshot>

Source

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.

Source

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).

Source

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.

Source

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.

Source

pub fn start_capture(&self)

A view started streaming. The first one turns capture on in the process.

Source

pub fn stop_capture(&self)

A view stopped streaming. The last one turns capture off.

Source

pub async fn attach_view(&self, view: &Arc<BrowserView>)

Bind a view to this producer so pushes reach it.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<S, T> Duplex<S> for T
where T: FromSample<S> + ToSample<S>,

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<S> FromSample<S> for S

Source§

fn from_sample_(s: S) -> S

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<F, T> IntoSample<T> for F
where T: FromSample<F>,

Source§

fn into_sample(self) -> T

Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> ToSample<U> for T
where U: FromSample<T>,

Source§

fn to_sample_(self) -> U

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more