Skip to content

Add opt-in resident worker lifecycle - #2465

Open
miguelg719 wants to merge 4 commits into
feat/resident-target-safetyfrom
feat/resident-gateway-runtime
Open

Add opt-in resident worker lifecycle#2465
miguelg719 wants to merge 4 commits into
feat/resident-target-safetyfrom
feat/resident-gateway-runtime

Conversation

@miguelg719

@miguelg719 miguelg719 commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • bootstrap the resident browser session when a privately configured MV3 worker starts
  • publish ready only after connection, V3Context target bootstrap, and host RPC receiver installation
  • coalesce startup and queue the first stagehand.init behind active bootstrap
  • leave ready immediately on socket loss, re-resolve, and reconnect with a bounded budget
  • restore initialized instrumentation before republishing ready
  • stop reconnecting after intentional close
  • preserve direct local/custom-CDP initialization outside the resident coordinator

Scope

Stack 4/6, based on the target-safety PR. service-worker-lifecycle/resident-runtime.ts coordinates 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

  • readiness cannot precede both browser bootstrap and receiver installation
  • explicit CDP initialization disables pending resident work before it can race
  • stale asynchronous operations cannot replace the current browser session
  • reconnect restores connection-bound page instrumentation before readiness

Validation

  • lifecycle coalescing, readiness, init-during-bootstrap, reconnect restoration, failure, and intentional-close tests
  • local/custom-CDP worker tests
  • complete TypeScript/package/rule suite at the stack tip

@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 90900b3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/server/service-worker.ts Outdated
Comment thread packages/server/service-worker-lifecycle/resident-runtime.ts
Comment thread packages/server/runtime.ts
Comment thread packages/server/runtime.ts
Comment thread packages/server/service-worker-lifecycle/resident-runtime.ts Outdated
@miguelg719
miguelg719 force-pushed the feat/resident-target-safety branch from 66eabdd to 908323a Compare July 27, 2026 23:08
@miguelg719
miguelg719 force-pushed the feat/resident-gateway-runtime branch from a391432 to 8a4a593 Compare July 27, 2026 23:11
@miguelg719 miguelg719 closed this Jul 27, 2026
@miguelg719
miguelg719 deleted the feat/resident-gateway-runtime branch July 27, 2026 23:11
@miguelg719
miguelg719 restored the feat/resident-gateway-runtime branch July 27, 2026 23:12
@miguelg719 miguelg719 reopened this Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant