Add opt in orange indicator when Stagehand controls the browser - #2457
Add opt in orange indicator when Stagehand controls the browser#2457antonvishal wants to merge 9 commits into
Conversation
|
|
This PR is from an external contributor and must be approved by a stagehand team member with write access before CI can run. |
There was a problem hiding this comment.
All reported issues were addressed across 20 files
Architecture diagram
sequenceDiagram
participant SDK as TS/Python SDK
participant Protocol as Protocol Layer
participant Runtime as StagehandRuntime
participant SW as Service Worker
participant Controller as AgentIndicatorController
participant Chrome as chrome.tabs/scripting API
participant Page as Browser Page
Note over SDK,Page: NEW: Opt-in Agent Indicator Flow
SDK->>Protocol: stagehand.init({agentIndicator: true})
Protocol->>Runtime: initialize() with agentIndicator: true
alt agentIndicator === true
Runtime->>Runtime: Store init state
Runtime->>SW: setAgentIndicatorActive(true)
SW->>Controller: create singular controller per session
Controller->>Chrome: tabs.query({})
Chrome-->>Controller: [tab12, tab34]
par For each tab
Controller->>Chrome: scripting.insertCSS({target:{tabId}, css, origin:"USER"})
Chrome->>Page: Inject :root::after style
Page-->>Chrome: CSS applied (pointer-events:none)
Chrome-->>Controller: Success
and Restricted page (e.g., chrome:// URL)
Chrome->>Controller: Error (tab skipped, best-effort)
end
Controller-->>SW: All tabs styled
SW-->>Runtime: Indicator active
else agentIndicator === false (default)
Runtime->>Runtime: No indicator activation
end
Runtime-->>Protocol: {initialized: true}
Protocol-->>SDK: init response
Note over Controller,Chrome: Navigation Re-application
alt User navigates to new page (status=loading)
Chrome->>Controller: tabs.onUpdated(tabId, "loading")
Controller->>Controller: Remove from styledTabs set
alt Indicator still active (desiredActive=true)
Controller->>Chrome: scripting.insertCSS({target:{tabId}})
Chrome->>Page: Re-apply indicator after navigation
end
end
Note over SDK,Page: Shutdown: Deactivate Indicator
SDK->>Protocol: stagehand.close()
Protocol->>Runtime: close()
Runtime->>Runtime: Check state: agentIndicator was true
Runtime->>SW: setAgentIndicatorActive(false)
SW->>Controller: setActive(false)
Controller->>Chrome: scripting.removeCSS() for each styled tab
Chrome->>Page: Remove :root::after style
Page-->>Chrome: Style removed
Chrome-->>Controller: Success
Controller-->>SW: All tabs cleaned
SW-->>Runtime: Indicator deactivated
Runtime->>Runtime: Proceed with session.close()
Note over Controller: Rapid state change protection
alt Controller.setActive(true) then immediately setActive(false)
Controller->>Controller: Serialize transitions via queue
Controller->>Controller: Skip stale activation (desiredActive already false)
Controller->>Chrome: Only removeCSS (no insertCSS)
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Confidence score: 4/5
- In
packages/server/agentIndicatorController.ts,navigationGenerationskeeps entries for tabs after close, so long-running sessions with frequent tab churn can accumulate stale state and steadily increase memory use, eventually degrading server responsiveness—add tab-close cleanup fornavigationGenerationsand the related cached tab state.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/server/agentIndicatorController.ts">
<violation number="1" location="packages/server/agentIndicatorController.ts:84">
P3: Long-running sessions retain one navigation-generation entry for every closed tab that ever loads, so memory grows with tab churn. Add tab-close cleanup for `navigationGenerations` (and the corresponding cached tab state).</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| let desiredActive = false; | ||
| let transition = Promise.resolve(); | ||
| const styledTabs = new Set<number>(); | ||
| const navigationGenerations = new Map<number, number>(); |
There was a problem hiding this comment.
P3: Long-running sessions retain one navigation-generation entry for every closed tab that ever loads, so memory grows with tab churn. Add tab-close cleanup for navigationGenerations (and the corresponding cached tab state).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/agentIndicatorController.ts, line 84:
<comment>Long-running sessions retain one navigation-generation entry for every closed tab that ever loads, so memory grows with tab churn. Add tab-close cleanup for `navigationGenerations` (and the corresponding cached tab state).</comment>
<file context>
@@ -81,6 +81,7 @@ export function createAgentIndicatorController(
let desiredActive = false;
let transition = Promise.resolve();
const styledTabs = new Set<number>();
+ const navigationGenerations = new Map<number, number>();
const injectionFor = (tabId: number): AgentIndicatorCssInjection => ({
</file context>
Why
Users need a simple visual cue showing when Stagehand is controlling the browser. This makes browser activity easier to understand without adding controls or changing page layout.
What changed
agentIndicator: trueinitialization option, disabled by default for backward compatibility.stagehand.init()and removes it duringstagehand.close().:root::afterstyle into the top frame, with no DOM nodes or iframe rendering.pointer-events: noneand leaves locator and XPath behavior unchanged.prefers-reduced-motionby disabling animation.agentIndicatorand Pythonagent_indicatorSDK options.Test plan
pnpm checkSummary by cubic
Adds an opt-in orange viewport halo that shows when Stagehand controls the browser. It runs from init to close, works across tabs and navigations, and doesn’t block interaction. Lifecycle is hardened against rapid state changes and navigation races.
New Features
agentIndicator(TS) /agent_indicator(Python) init option tostagehand.init; default isfalse.:root::afterhalo withpointer-events: none; respectsprefers-reduced-motion; visible in screenshots.stagehand.init()and deactivates onstagehand.close(); applies to existing/new tabs and re-applies after navigation and in-flight navigations.chrome.scripting.insertCSS/removeCSS(USER origin), serializes rapid state transitions, retries on tab query failures, best-effort on restricted pages; never blocks the session.Migration
agentIndicator: truein TS oragent_indicator=Truein Python when constructingStagehand.Written for commit 15b53f4. Summary will update on new commits.