Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/deployment-operator-cd-browser-use.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: CD / Browser Use Sidecar

on:
pull_request:
branches:
- "master"
paths:
- ".github/workflows/deployment-operator-cd-browser-use.yaml"
- "go/deployment-operator/dockerfiles/browser-use/**"
push:
tags:
- 'go/deployment-operator/v*.*.*'
workflow_dispatch:
inputs:
version:
description: 'Semantic Version (e.g. 0.1.0)'
required: true
type: string
browser_use_version:
description: 'Pinned browser-use PyPI version baked into the image'
required: false
type: string

permissions:
contents: read

env:
DOCKER_METADATA_PR_HEAD_SHA: 'true'
REGISTRY_IMAGE: pluralsh/browser-use
BROWSER_USE_DEFAULT_VERSION: 0.1.0

jobs:
build-and-publish:
name: Build and push browser-use sidecar image
permissions:
contents: read
id-token: write
packages: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/pluralsh/browser-use
docker.io/pluralsh/browser-use
tags: |
type=raw,value=${{ inputs.version }},enable=${{ inputs.version != '' }},priority=1100
type=semver,pattern={{version}},value=${{ github.ref_name }},match=go/deployment-operator/v(\d+\.\d+\.\d+)$,priority=1000
type=sha,priority=800
type=ref,event=pr,priority=600
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: mjgpluralsh
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: "./go/deployment-operator/dockerfiles/browser-use"
file: "./go/deployment-operator/dockerfiles/browser-use/Dockerfile"
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BROWSER_USE_VERSION=${{ inputs.browser_use_version || env.BROWSER_USE_DEFAULT_VERSION }}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ const columns = [
</Chip>
),
}),
columnHelper.accessor((runtime) => runtime.browserEnabled, {
id: 'browserEnabled',
header: 'Browser',
cell: ({ getValue }) => (
<Chip
fillLevel={0}
size="small"
severity={getValue() ? 'success' : 'neutral'}
tooltip={
getValue()
? 'A headless browser sidecar and the browser-use MCP tools are provisioned for runs on this runtime.'
: 'Runs on this runtime do not have browser capabilities. Set spec.browser.enabled on the AgentRuntime CRD to enable.'
}
>
{getValue() ? 'Enabled' : 'Disabled'}
</Chip>
),
}),
columnHelper.accessor((runtime) => runtime.cluster, {
id: 'cluster',
header: 'Cluster',
Expand Down
13 changes: 9 additions & 4 deletions assets/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,8 @@ export type AgentRuntime = {
allowedRepositories?: Maybe<Array<Maybe<Scalars['String']['output']>>>;
/** default interval in seconds between babysit checks for runs on this runtime */
babysitInterval?: Maybe<Scalars['Int']['output']>;
/** whether this runtime provisions a headless browser sidecar (and the browser-use MCP tools) for runs */
browserEnabled?: Maybe<Scalars['Boolean']['output']>;
/** the cluster this runtime is running on */
cluster?: Maybe<Cluster>;
/** the policy for creating runs on this runtime */
Expand Down Expand Up @@ -629,6 +631,8 @@ export type AgentRuntimeAttributes = {
allowedRepositories?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>;
/** default interval in seconds between babysit checks for runs on this runtime */
babysitInterval?: InputMaybe<Scalars['Int']['input']>;
/** whether this runtime provisions a headless browser sidecar (and the browser-use MCP tools) for runs */
browserEnabled?: InputMaybe<Scalars['Boolean']['input']>;
/** the policy for creating runs on this runtime */
createBindings?: InputMaybe<Array<InputMaybe<AgentBindingAttributes>>>;
/** whether this is the default runtime for coding agents */
Expand Down Expand Up @@ -16808,7 +16812,7 @@ export type AgentRunTinyFragment = { __typename?: 'AgentRun', id: string, status

export type AgentRunFragment = { __typename?: 'AgentRun', id: string, status: AgentRunStatus, mode: AgentRunMode, babysit?: boolean | null, prompt: string, shared?: boolean | null, error?: string | null, repository: string, branch?: string | null, insertedAt?: string | null, updatedAt?: string | null, messages?: Array<{ __typename?: 'AgentMessage', id: string, seq: number, role: AiRole, message: string, cost?: { __typename?: 'AgentMessageCost', total: number, tokens?: { __typename?: 'AgentMessageTokens', input?: number | null, output?: number | null, reasoning?: number | null } | null } | null, metadata?: { __typename?: 'AgentMessageMetadata', reasoning?: { __typename?: 'AgentMessageReasoning', text?: string | null, start?: number | null, end?: number | null } | null, file?: { __typename?: 'AgentMessageFile', name?: string | null, text?: string | null, start?: number | null, end?: number | null } | null, tool?: { __typename?: 'AgentMessageTool', name?: string | null, state?: AgentMessageToolState | null, input?: string | null, output?: string | null } | null } | null } | null> | null, todos?: Array<{ __typename?: 'AgentTodo', title: string, description: string, done?: boolean | null } | null> | null, analysis?: { __typename?: 'AgentAnalysis', summary: string, analysis: string, bullets?: Array<string | null> | null } | null, runtime?: { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType } | null, pullRequests?: Array<{ __typename?: 'PullRequest', id: string, url: string, title?: string | null, creator?: string | null, status?: PrStatus | null, insertedAt?: string | null, updatedAt?: string | null } | null> | null, podReference?: { __typename?: 'AgentPodReference', name: string, namespace: string } | null };

export type AgentRuntimeFragment = { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType, aiProxy?: boolean | null, default?: boolean | null, cluster?: { __typename?: 'Cluster', self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null };
export type AgentRuntimeFragment = { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType, aiProxy?: boolean | null, browserEnabled?: boolean | null, default?: boolean | null, cluster?: { __typename?: 'Cluster', self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null };

export type AgentRuntimeReposFragment = { __typename?: 'AgentRuntime', id: string, allowedRepositories?: Array<string | null> | null };

Expand Down Expand Up @@ -16866,14 +16870,14 @@ export type AgentRuntimesQueryVariables = Exact<{
}>;


export type AgentRuntimesQuery = { __typename?: 'RootQueryType', agentRuntimes?: { __typename?: 'AgentRuntimeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'AgentRuntimeEdge', node?: { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType, aiProxy?: boolean | null, default?: boolean | null, cluster?: { __typename?: 'Cluster', self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null } | null } | null> | null } | null };
export type AgentRuntimesQuery = { __typename?: 'RootQueryType', agentRuntimes?: { __typename?: 'AgentRuntimeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null, hasPreviousPage: boolean, startCursor?: string | null }, edges?: Array<{ __typename?: 'AgentRuntimeEdge', node?: { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType, aiProxy?: boolean | null, browserEnabled?: boolean | null, default?: boolean | null, cluster?: { __typename?: 'Cluster', self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null } | null } | null> | null } | null };

export type AgentRuntimeQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;


export type AgentRuntimeQuery = { __typename?: 'RootQueryType', agentRuntime?: { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType, aiProxy?: boolean | null, default?: boolean | null, cluster?: { __typename?: 'Cluster', self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null } | null };
export type AgentRuntimeQuery = { __typename?: 'RootQueryType', agentRuntime?: { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType, aiProxy?: boolean | null, browserEnabled?: boolean | null, default?: boolean | null, cluster?: { __typename?: 'Cluster', self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null } | null };

export type AgentRuntimeReposQueryVariables = Exact<{
id: Scalars['ID']['input'];
Expand Down Expand Up @@ -16920,7 +16924,7 @@ export type UpsertAgentRuntimeMutationVariables = Exact<{
}>;


export type UpsertAgentRuntimeMutation = { __typename?: 'RootMutationType', upsertAgentRuntime?: { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType, aiProxy?: boolean | null, default?: boolean | null, cluster?: { __typename?: 'Cluster', self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null } | null };
export type UpsertAgentRuntimeMutation = { __typename?: 'RootMutationType', upsertAgentRuntime?: { __typename?: 'AgentRuntime', id: string, name: string, type: AgentRuntimeType, aiProxy?: boolean | null, browserEnabled?: boolean | null, default?: boolean | null, cluster?: { __typename?: 'Cluster', self?: boolean | null, virtual?: boolean | null, id: string, name: string, handle?: string | null, distro?: ClusterDistro | null, upgradePlan?: { __typename?: 'ClusterUpgradePlan', compatibilities?: boolean | null, deprecations?: boolean | null, incompatibilities?: boolean | null } | null, provider?: { __typename?: 'ClusterProvider', name: string, cloud: string } | null } | null, createBindings?: Array<{ __typename?: 'PolicyBinding', id?: string | null, user?: { __typename?: 'User', id: string, name: string, email: string } | null, group?: { __typename?: 'Group', id: string, name: string } | null } | null> | null } | null };

export type ShareAgentRunMutationVariables = Exact<{
id: Scalars['ID']['input'];
Expand Down Expand Up @@ -21104,6 +21108,7 @@ export const AgentRuntimeFragmentDoc = gql`
name
type
aiProxy
browserEnabled
cluster {
...ClusterTiny
}
Expand Down
1 change: 1 addition & 0 deletions assets/src/graph/ai/agent.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fragment AgentRuntime on AgentRuntime {
name
type
aiProxy
browserEnabled
cluster {
...ClusterTiny
}
Expand Down
7 changes: 7 additions & 0 deletions go/client/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions go/client/graph/agentruntime.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ fragment AgentRuntimeFragment on AgentRuntime {
name
type
aiProxy
browserEnabled
cluster {
...TinyClusterFragment
}
Expand Down
4 changes: 4 additions & 0 deletions go/client/models_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions go/deployment-operator/api/v1alpha1/agentruntime_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -750,10 +750,11 @@ func (in *AgentRuntime) ConsoleID() *string {

func (in *AgentRuntime) Attributes() console.AgentRuntimeAttributes {
attrs := console.AgentRuntimeAttributes{
Name: in.ConsoleName(),
Default: in.Spec.Default,
Type: in.Spec.Type,
AiProxy: in.Spec.AiProxy,
Name: in.ConsoleName(),
Default: in.Spec.Default,
Type: in.Spec.Type,
AiProxy: in.Spec.AiProxy,
BrowserEnabled: lo.ToPtr(in.Spec.Browser.IsEnabled()),
}
if in.Spec.Bindings != nil {
attrs.CreateBindings = algorithms.Map(in.Spec.Bindings.Create, func(b Binding) *console.AgentBindingAttributes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ This section records the **original user prompt** for this agent run. It is embe
- Perform **static, read‑only** analysis of code and configuration.
- Produce a structured **Markdown** report in memory.
- **Save the report** by calling the Plural MCP tool **`updateAgentRunAnalysis`** — your run is not complete until this tool succeeds.
- You MUST NOT change repository or host state.
- You MUST NOT change repository or host state.{{ if .BrowserEnabled }}
- A read-only browser is available through the `browser` MCP server
(`mcp__browser__open`, `mcp__browser__state`, `mcp__browser__screenshot`,
...). You MAY use it to load already-public URLs and capture screenshots
as evidence for findings, but MUST NOT submit forms, click destructive
actions, or otherwise mutate remote state.{{ end }}

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ Docker-in-Docker is enabled. You can use `docker` and `docker-compose` for testi
---
{{ end }}

{{ if .BrowserEnabled }}
## Browser runtime

The `browser` MCP server (browser-use over a headless browser sidecar) is
available in this run. When reviewer feedback or a failing CI check requires
exercising a web UI, prefer the structured `mcp__browser__*` tools
(`open`, `state`, `click`, `type`, `screenshot`, `keys`, `eval`, ...) over
writing ad-hoc Playwright/Puppeteer/Selenium scripts. If the MCP tools are
unavailable, fall back to a remote driver against
`http://127.0.0.1:3000`.

---
{{ end }}

## Plural MCP tool hints

### `downloadServiceManifests`
Expand Down
Loading
Loading