Add opt-in resident worker lifecycle - #2465
Open
miguelg719 wants to merge 4 commits into
Open
Conversation
|
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 9 files
Architecture diagram
sequenceDiagram
participant SW as ServiceWorker (startStagehandServiceWorker)
participant Lifecycle as ResidentRuntimeLifecycle
participant Runtime as StagehandRuntime
participant Factory as StagehandBrowserSessionFactory (V3Context)
participant Proxy as Browser Proxy WebSocket (CDP)
participant Router as RPCRouter
participant Controller as ContextController
participant Client as Host App (RPC Client)
Note over SW,Client: NEW: Resident MV3 Lifecycle Flow
SW->>SW: Determine browserProxyUrl / resolveResidentWebSocketUrl
alt Resident mode configured
SW->>Lifecycle: new ResidentRuntimeLifecycle(runtime, options)
Lifecycle->>Lifecycle: marker.state = "connecting" / "unconfigured"
opt autoBootstrap = true (default)
SW->>Lifecycle: bootstrap()
end
else No resident config (local/custom CDP)
Note over SW,Client: Direct initialization path unchanged
end
Lifecycle->>Lifecycle: enqueue runResidentBootstrap(generation)
alt bootstrap succeeds
Lifecycle->>Lifecycle: resolveResidentWebSocketUrl()
Proxy->>Lifecycle: WebSocket URL
Lifecycle->>Runtime: configureLoopback({cdpUrl}, lifecycle hooks)
Runtime->>Factory: browserSessionFactory(cdpUrl, logger, {bootstrapMode:"resident", onConnected, onDisconnected})
Factory->>Proxy: V3Context.create() – WebSocket connect
Proxy-->>Factory: Connected
Factory->>Factory: onConnected() callback
Factory-->>Lifecycle: Session created
Lifecycle->>Lifecycle: marker.state = "bootstrapping", connected=true
Runtime->>Runtime: restoreInitializedBrowserSession() if previously initialized
Note over Runtime: Re-applies init scripts, headers, domain policy
Lifecycle->>Lifecycle: await waitForRpcReceiver()
Router->>Lifecycle: receiverReady resolved (RPC receiver installed)
Lifecycle->>Lifecycle: publish("ready", connected=true)
Lifecycle-->>SW: bootstrap() resolves
SW-->>Client: Service worker ready, marker.state = "ready"
else bootstrap fails (socket loss or error)
Runtime->>Lifecycle: onDisconnected() called
Lifecycle->>Lifecycle: marker.state = "reconnecting", connected=false
Lifecycle->>Lifecycle: scheduleReconnect() with bounded budget
alt Reconnect budget not exhausted
Lifecycle->>Lifecycle: setTimeout (delay)
Note over Lifecycle: After delay, new generation, bootstrap() called again
Lifecycle->>Lifecycle: resolveResidentWebSocketUrl() (re-resolve)
Lifecycle->>Runtime: configureLoopback({cdpUrl}) – new session
Runtime->>Factory: create new browser session
Factory->>Proxy: reconnect
Proxy-->>Factory: reconnected
Factory->>Lifecycle: onConnected()
Lifecycle->>Lifecycle: restoreInitializedBrowserSession() + waitForRpcReceiver()
Lifecycle->>Lifecycle: publish("ready")
else Reconnect budget exhausted
Lifecycle->>Lifecycle: publishFailure("reconnecting", "budget exhausted")
Lifecycle->>Lifecycle: marker.state = "failed"
end
end
Note over Client,Router: Initialize Stagehand (first time or after reconnect)
Client->>Router: RPC "initialize" (no browserCdpUrl)
Router->>Lifecycle: initialize(params)
Lifecycle->>Lifecycle: ensure bootstrap() completed and state ready
Lifecycle->>Runtime: runtime.initialize(params)
Runtime->>Runtime: set logLevel, init scripts, etc.
Runtime-->>Lifecycle: result { initialized: true }
Lifecycle-->>Router: result
Router-->>Client: response
Note over Client,Lifecycle: Close context (delegated to lifecycle)
Client->>Router: RPC "context.close"
Router->>Controller: close() (with options.close)
Controller->>Lifecycle: lifecycle.close()
Lifecycle->>Lifecycle: cancelReconnects(), closed = true
Lifecycle->>Runtime: runtime.close()
Runtime->>Runtime: increment browserSessionGeneration, close session
Lifecycle->>Lifecycle: publish("closed", false)
Lifecycle-->>Controller: done
Controller-->>Router: { closed: true }
Router-->>Client: response
Note over SW,Client: Runtime marker exposed on scope.__stagehand_runtime
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
miguelg719
force-pushed
the
feat/resident-target-safety
branch
from
July 27, 2026 23:08
66eabdd to
908323a
Compare
miguelg719
force-pushed
the
feat/resident-gateway-runtime
branch
from
July 27, 2026 23:11
a391432 to
8a4a593
Compare
…-runtime # Conflicts: # packages/server/runtime.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
stagehand.initbehind active bootstrapScope
Stack 4/6, based on the target-safety PR.
service-worker-lifecycle/resident-runtime.tscoordinates only startup, same-session reconnect, queued initialization, and close. It contains no cross-reservation activation or reset protocol. Existing heartbeat behavior is unchanged.Review focus
Validation