pub struct BrowserViewRegistry { /* private fields */ }Expand description
Every browser the drawer can reach, keyed by conversation/agent-session — plus the one standing session shared by every conversation that has no agent-attached browser of its own (controller ruling R6).
Implementations§
Source§impl BrowserViewRegistry
impl BrowserViewRegistry
pub fn new(root: PathBuf) -> Self
Sourcepub fn set_signin_attention(&self, attention: Arc<dyn SignInAttention>)
pub fn set_signin_attention(&self, attention: Arc<dyn SignInAttention>)
Install the operator-attention sink relayed views report sign-in waits
through. Called once, by ServerState::with_config; a second call is a
silent no-op.
Sourcepub async fn standing(&self) -> Arc<BrowserView> ⓘ
pub async fn standing(&self) -> Arc<BrowserView> ⓘ
The standing user session, created on first reference.
Creating it does NOT launch Chromium — BrowserTools launches
lazily, and for this view the trigger is the user’s first navigation
(see BrowserTools::user_navigate). Opening the drawer on an empty
standing session therefore costs nothing and shows the empty state,
which is exactly what the design asks for.
Sourcepub async fn register(
&self,
conversation_id: impl Into<String>,
tools: Arc<BrowserTools>,
) -> Arc<BrowserView> ⓘ
pub async fn register( &self, conversation_id: impl Into<String>, tools: Arc<BrowserTools>, ) -> Arc<BrowserView> ⓘ
Publish an agent-attached browser under a conversation/agent-session key, so the drawer can watch that conversation specifically.
This is the producer API. Anything that builds an assistant
runtime hands the runtime’s own BrowserTools
(AssistantRuntime::browser) in here; the registry never creates an
agent’s browser itself. Registering costs nothing for a run that never
browses — BrowserTools launches Chromium lazily.
Replacement is the lifetime bound. A conversation’s view outlives
the run that created it (see BrowserView::note_run_ended), so the thing that
eventually releases the old browser is a NEW run registering for the
SAME key. That makes the standing cost one idle Chromium per
conversation whose agent actually browsed, not one per run.
Subscribers and the event cursor are handed over to the new view, so a drawer watching this conversation follows it to the new browser without re-subscribing — and, critically, without the cursor going backwards, which would break gap detection far worse than a gap does.
Sourcepub async fn register_relay(
&self,
conversation_id: impl Into<String>,
producer: Arc<RelayProducer>,
) -> Arc<BrowserView> ⓘ
pub async fn register_relay( &self, conversation_id: impl Into<String>, producer: Arc<RelayProducer>, ) -> Arc<BrowserView> ⓘ
Publish a SUPERVISED AGENT PROCESS’s browser under a conversation key.
The relay twin of Self::register, with one difference that matters:
the same process re-claiming its own conversation is a no-op. A
supervised agent registers on every turn, and churning the view each
time would reset its cursor and drop the drawer’s stream for no reason.
A DIFFERENT process claiming the key — the supervisor restarted it —
replaces the view through the ordinary adopt path, so a drawer that
never unsubscribed follows the agent across without the cursor moving
backwards.
Sourcepub async fn producer_for(
&self,
client_id: &str,
agent_id: &str,
channel: &Arc<WsChannel>,
) -> Arc<RelayProducer> ⓘ
pub async fn producer_for( &self, client_id: &str, agent_id: &str, channel: &Arc<WsChannel>, ) -> Arc<RelayProducer> ⓘ
The producer for an agent connection, created on its first registration.
Sourcepub async fn broadcast_host_connected(&self, connected: bool)
pub async fn broadcast_host_connected(&self, connected: bool)
Tell every supervised agent process that host connectivity changed.
They cache the answer — a supervised process has no read of the
daemon’s session set — and it decides whether browser_await_signin
points the user at the drawer or tells them to open the CAR app.
Sourcepub async fn producer(&self, client_id: &str) -> Option<Arc<RelayProducer>>
pub async fn producer(&self, client_id: &str) -> Option<Arc<RelayProducer>>
The producer a given connection registered, if any. The gate on inbound pushes: a connection that never registered has none.
Sourcepub async fn conversation_owner(&self, conversation_id: &str) -> Option<String>
pub async fn conversation_owner(&self, conversation_id: &str) -> Option<String>
The agent entitled to publish a conversation, and how that gets
recorded. See browser_relay::authorize_conversation_claim.
pub async fn bind_conversation(&self, conversation_id: &str, agent_id: &str)
Sourcepub async fn note_producer_disconnected(&self, client_id: &str)
pub async fn note_producer_disconnected(&self, client_id: &str)
An agent connection dropped — its browser went with the process. Its views stay registered, reporting an empty browser, so a restarted process can replace them and carry the drawer across.
Sourcepub async fn release_if_idle(&self, view: &Arc<BrowserView>) -> bool
pub async fn release_if_idle(&self, view: &Arc<BrowserView>) -> bool
Release a view whose run has ended and that nobody is subscribed to: drop it from the map (and with it the last reference to the browser it was serving) and stop its capture. Reports whether it went.
This is the eviction path the lifetime bound needs. Replacement —
“a NEW run registering for the SAME key” — is the documented bound,
and it is genuinely unreachable for a producer that mints a fresh key
per run: the in-daemon assistant_start path keys on
mcp-run-<uuid>, so before this, every assistant run that browsed
left one idle Chromium registered for the daemon’s whole lifetime.
It does NOT weaken “a view outlives the run that created it”. A subscribed view is never released, so the drawer still keeps showing the last page exactly as the agent left it, with agent-opened tabs usable, for as long as anything is actually watching. What goes is a finished run’s browser that no drawer ever attached to — which nobody can observe, and which is precisely the leak.
Identity-checked, never key-checked: a successor may already hold the
key by the time an asynchronous run-end signal arrives, and removing
by key would take a LIVE run’s browser down. Same reasoning as
BrowserView::note_run_ended being a method on the view.
Sourcepub async fn pending_signins(&self) -> Vec<BrowserSignInSnapshot>
pub async fn pending_signins(&self) -> Vec<BrowserSignInSnapshot>
Durable state returned by host.subscribe, independent of the bounded
host-event backlog. Relay producers are deduplicated because every view
they back shows the same one process-owned browser.
pub async fn get( &self, conversation_id: Option<&str>, ) -> Option<Arc<BrowserView>>
Sourcepub async fn drop_subscriptions_for_client(&self, client_id: &str)
pub async fn drop_subscriptions_for_client(&self, client_id: &str)
Disconnect cleanup: drop this connection’s subscriptions everywhere, and start the control grace period on any view it was driving.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for BrowserViewRegistry
impl !RefUnwindSafe for BrowserViewRegistry
impl !UnwindSafe for BrowserViewRegistry
impl Send for BrowserViewRegistry
impl Sync for BrowserViewRegistry
impl Unpin for BrowserViewRegistry
impl UnsafeUnpin for BrowserViewRegistry
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