feat: add Stagehand code execution tool - #2619
Conversation
|
There was a problem hiding this comment.
All reported issues were addressed across 18 files
Architecture diagram
sequenceDiagram
participant Host as Agent Framework / Host
participant Exec as StagehandCodeExecutor
participant Queue as Serial Queue
participant Snip as executeStagehandSnippet
participant Stagehand as Stagehand Instance
participant Browser as Browser (local/Browserbase)
participant Page as Page
participant MCP as MCP Server / Tool
participant Config as stagehandCodeConfigFromEnv
Note over Host,Config: Startup: Configuration and Browser Selection
Host->>Config: Read environment variables
Config->>Config: Determine browser type (local/browserbase)
alt STAGEHAND_BROWSER=local
Config->>Config: Set local headless browser
else STAGEHAND_BROWSER=browserbase
Config->>Config: Validate BROWSERBASE_API_KEY present
Config->>Config: Forward project ID if set
else No explicit setting
alt BROWSERBASE_API_KEY exists
Config->>Config: Select Browserbase
else
Config->>Config: Select local headless
end
end
Config->>Config: Resolve model name and API key
alt Explicit STAGEHAND_MODEL_NAME
Config->>Config: Use provider-specific API key
alt Provider is Anthropic
Config->>Config: Add dangerous-direct-browser-access header
end
else No model name, Google key present
Config->>Config: Default to google/gemini-2.5-flash-lite
end
Config-->>Host: Return StagehandCodeConfig
Note over Host,MCP: Execution Flow
Host->>Exec: new StagehandCodeExecutor(config)
Host->>Exec: execute({ code }, signal?)
Note over Exec,Queue: Serialization and Validation
Exec->>Exec: validate input (size, non-empty)
alt Invalid input
Exec-->>Host: Return failure with kind="validation"
else Valid input
Exec->>Queue: Chain onto FIFO queue
Queue-->>Exec: Wait for previous operations
end
Note over Exec,Stagehand: Lazy Browser Initialization
Exec->>Exec: ensureStagehand()
alt Stagehand not yet created
Exec->>Browser: Launch (local or browserbase)
alt Launch successful
Exec->>Stagehand: Stagehand.create(browser, config)
alt Stagehand create fails
Exec->>Browser: Close browser
Exec-->>Host: Return failure with kind="runtime"
end
else Launch fails
Exec-->>Host: Return failure with kind="runtime"
end
end
Exec-->>Exec: Stagehand instance ready
Note over Exec,Page: Snippet Execution
Exec->>Page: Get active page (or first/new)
alt Signal aborted before execution
Exec-->>Host: Return failure with kind="aborted"
else Signal not aborted
Exec->>Snip: executeStagehandSnippet({ code, page, context, stagehand, console })
Snip->>Snip: Create AsyncFunction with bindings
Note over Snip: Injects page, context, stagehand, z (Zod), console
Snip->>Page: Execute snippet code
alt Snippet succeeds
Page-->>Snip: Return value
Snip-->>Exec: Return value
Exec->>Page: Read page state (URL, title)
Exec-->>Host: Return success with page state and value
else Snippet throws
Page-->>Snip: Throw error
Snip-->>Exec: Throw error
Exec->>Exec: normalizeError (redact secrets)
Exec-->>Host: Return failure with kind="runtime"
end
end
Note over Exec: Cleanup and Shutdown
Host->>Exec: close()
Exec->>Exec: Drain queued operations
Exec->>Stagehand: stagehand.close()
Exec->>Browser: browser.close()
alt Both close fail
Exec-->>Host: AggregateError
end
Exec-->>Host: Cleanup complete
Note over MCP,MCP: MCP Tool Registration
Host->>MCP: createCodeModeMcpServer(executor)
MCP->>MCP: registerTool("code_execute", schema, handler)
MCP-->>Host: McpServer ready
Host->>MCP: connect to stdio transport
Note over MCP,Host: Tool calls flow through MCP protocol
Host->>MCP: callTool("code_execute", { code })
MCP->>Exec: executor.execute(input, signal)
Exec-->>MCP: result
MCP->>MCP: Format as text + structured content
alt result.ok
MCP-->>Host: isError=false, content=JSON
else
MCP-->>Host: isError=true, content=JSON
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Why
This second stack layer adds the independently usable code-execution product core on top of the package and MCP host from #2597. Keeping execution separate from generated agent guidance lets reviewers focus on browser ownership, configuration, schemas, queueing, redaction, and lifecycle behavior.
Stack
code_execute, Stagehand executor, local/Browserbase configuration, schemas, and runtime testsSKILL.md,REFERENCE.md, generated exports, package assets, and guidance loading checksWhat changed
StagehandCodeExecutorwith lazy browser startup and one long-lived session per executorcode_executepage,context,stagehand, Zod, and a bounded console into async JavaScript snippetsLocal and remote startup
The stdio process reads startup configuration from its environment:
STAGEHAND_BROWSER=localSTAGEHAND_BROWSER=browserbaseBROWSERBASE_API_KEY.BROWSERBASE_PROJECT_IDis forwarded when present. Explicit model names select only their matching provider key, and Google-key precedence matches the eval-native configuration.Timeout and cancellation boundary
An abort signal can cancel queued work before its snippet begins. Arbitrary JavaScript already executing in-process cannot be safely preempted. If code blocks the Node event loop, the owning framework must terminate the entire child process tree, escalate to
SIGKILLafter its deadline, and create a replacement process. Killing only the Node process can leave a local browser descendant alive.Intentionally not included
SKILL.mdorREFERENCE.mdE2E Test Matrix
pnpm --filter @browserbasehq/stagehand-integrations typecheck && pnpm --filter @browserbasehq/stagehand-integrations testcode_executewas discovered and state persisted across both calls.code_executewas discovered and both remote pages persisted.pnpm exec turbo run fmt:check lint typecheck --concurrency=1Changeset
None. This changes a private workspace package and does not publish a release.