Skip to content

Commit 3dbf864

Browse files
committed
feat(shell): use Rust browser connection module
- Remove TS Playwright browser runtime and vendored Rust duplicate from docker-git - Install ProverCoderAI/rust-browser-connection as the single noVNC/CDP browser provider - Preserve invariant: generated Playwright MCP startup calls docker-git-browser-connection start --project <id> exactly once before MCP config
1 parent 572a163 commit 3dbf864

153 files changed

Lines changed: 239 additions & 17159 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/app/src/lib/core/templates-entrypoint.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,9 @@ import { renderEntrypointGitConfig, renderEntrypointGitHooks } from "./templates
2727
import { renderEntrypointGrokConfig } from "./templates-entrypoint/grok.js"
2828
import { renderEntrypointDockerGitBootstrap } from "./templates-entrypoint/nested-docker-git.js"
2929
import { renderEntrypointOpenCodeConfig } from "./templates-entrypoint/opencode.js"
30-
import { renderEntrypointPlaywrightBrowserRuntime } from "./templates-entrypoint/playwright-browser.js"
3130
import { renderEntrypointProjectAgentRules } from "./templates-entrypoint/project-rules.js"
3231
import { renderEntrypointRtkConfig } from "./templates-entrypoint/rtk.js"
33-
import { renderEntrypointBackgroundTasks } from "./templates-entrypoint/tasks.js"
32+
import { renderEntrypointBackgroundTasks, renderEntrypointRustBrowserConnection } from "./templates-entrypoint/tasks.js"
3433
import {
3534
renderEntrypointBashCompletion,
3635
renderEntrypointBashHistory,
@@ -60,7 +59,7 @@ export const renderEntrypoint = (config: TemplateConfig): string =>
6059
renderEntrypointProjectAgentRules(),
6160
renderEntrypointAgentsNotice(config),
6261
renderEntrypointDockerSocket(config),
63-
renderEntrypointPlaywrightBrowserRuntime(config),
62+
renderEntrypointRustBrowserConnection(),
6463
renderEntrypointMcpPlaywright(config),
6564
renderEntrypointGitConfig(config),
6665
renderEntrypointClaudeConfig(config),

packages/app/src/lib/core/templates-entrypoint/playwright-browser.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.

packages/app/src/lib/core/templates-entrypoint/tasks.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,51 @@ fi
268268
269269
${renderAgentLaunch(config)}
270270
) &`
271+
// CHANGE: start the external Rust browser module from the project entrypoint.
272+
// WHY: issue #347 moves browser ownership to ProverCoderAI/rust-browser-connection while keeping docker-git as caller.
273+
// QUOTE(ТЗ): "Вынести noVNC + MCP Playright в единый модуль."
274+
// REF: issue-347
275+
// SOURCE: n/a
276+
// FORMAT THEOREM: MCP_PLAYWRIGHT_ENABLE=1 -> eventually running(DOCKER_GIT_BROWSER_CONTAINER_NAME)
277+
// PURITY: SHELL
278+
// EFFECT: generated bash calls docker-git-browser-connection, which calls Docker.
279+
// INVARIANT: browser shares the project container network namespace, so CDP is http://127.0.0.1:9223 from agents.
280+
// COMPLEXITY: O(1) entrypoint orchestration; Docker build/run is delegated to Rust.
281+
export const renderEntrypointRustBrowserConnection = (): string => [
282+
'# Unified Rust browser connection (noVNC + CDP) for MCP Playwright + Hermes — per #347.',
283+
'# Defaults are safe no-ops unless MCP Playwright is enabled.',
284+
'docker_git_start_rust_browser_connection() {',
285+
' if [[ "${MCP_PLAYWRIGHT_ENABLE:-0}" != "1" ]]; then',
286+
' return 0',
287+
' fi',
288+
'',
289+
' local browser_bin=""',
290+
' local candidate',
291+
' for candidate in /root/.cargo/bin/docker-git-browser-connection /usr/local/cargo/bin/docker-git-browser-connection $(command -v docker-git-browser-connection 2>/dev/null || true); do',
292+
' if [[ -x "$candidate" ]]; then',
293+
' browser_bin="$candidate"',
294+
' break',
295+
' fi',
296+
' done',
297+
'',
298+
' if [[ -z "$browser_bin" ]]; then',
299+
' echo "[browser] WARNING: docker-git-browser-connection not found; Playwright MCP browser is unavailable" >&2',
300+
' MCP_PLAYWRIGHT_ENABLE=0',
301+
' export MCP_PLAYWRIGHT_ENABLE',
302+
' return 0',
303+
' fi',
304+
'',
305+
' local project_container="${DOCKER_GIT_PROJECT_CONTAINER_NAME:-$(hostname)}"',
306+
' local network_mode="container:${project_container}"',
307+
' mkdir -p /var/log',
308+
' "$browser_bin" start --project "$project_container" --network "$network_mode" >> /var/log/docker-git-browser.log 2>&1 || {',
309+
' echo "[browser] WARNING: Rust browser connection failed; see /var/log/docker-git-browser.log" >&2',
310+
' MCP_PLAYWRIGHT_ENABLE=0',
311+
' export MCP_PLAYWRIGHT_ENABLE',
312+
' return 0',
313+
' }',
314+
' echo "[browser] Rust browser connection is ready via $browser_bin on $network_mode"',
315+
'}',
316+
'',
317+
'docker_git_start_rust_browser_connection',
318+
].join("\n")

packages/app/src/lib/core/templates.ts

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,34 @@
22
import type { TemplateConfig } from "./domain.js"
33
import type { ResolvedComposeResourceLimits } from "./resource-limits.js"
44
import { renderEntrypoint } from "./templates-entrypoint.js"
5-
import { type ComposeResourceLimits, renderDockerCompose } from "./templates/docker-compose.js"
6-
import { renderDockerfile } from "./templates/dockerfile.js"
7-
import { renderPlaywrightBrowserRuntime } from "./templates/playwright-browser-runtime.js"
85
import {
9-
renderPlaywrightBrowserDockerfile,
10-
renderPlaywrightCdpGuard,
11-
renderPlaywrightStartExtra
12-
} from "./templates/playwright.js"
6+
type ComposeResourceLimits,
7+
type DockerComposeRenderOptions,
8+
renderDockerCompose
9+
} from "./templates/docker-compose.js"
10+
import { renderDockerfile } from "./templates/dockerfile.js"
11+
12+
// NOTE (Rust migration #347):
13+
// The unified single-browser (noVNC + CDP) is now managed by the Rust binary
14+
// `docker-git-browser-connection` (separate repo ProverCoderAI/rust-browser-connection).
15+
// It guarantees exactly one `dg-{project}-browser` container per project.
16+
// Old separate Dockerfile.browser / docker-git-browser-runtime.sh / cdp-guard
17+
// have been replaced to avoid duplication with TS code.
18+
// The Rust CLI is started in background from entrypoint (see renderEntrypointRustBrowserConnection).
19+
// MCP Playwright connects to the CDP exposed by the shared browser container.
1320

1421
export type FileSpec =
1522
| { readonly _tag: "File"; readonly relativePath: string; readonly contents: string; readonly mode?: number }
1623
| { readonly _tag: "Dir"; readonly relativePath: string }
1724

25+
export type TemplateRenderOptions = {
26+
readonly compose: DockerComposeRenderOptions
27+
}
28+
29+
const defaultTemplateRenderOptions: TemplateRenderOptions = {
30+
compose: { enableLocalDockerSocket: false }
31+
}
32+
1833
const renderGitignore = (): string =>
1934
`# docker-git project files
2035
# NOTE: bootstrap secrets stay local-only and should not be committed.
@@ -51,39 +66,21 @@ const renderConfigJson = (config: TemplateConfig): string =>
5166

5267
export const planFiles = (
5368
config: TemplateConfig,
54-
composeResourceLimits?: ResolvedComposeResourceLimits | ComposeResourceLimits
69+
composeResourceLimits?: ResolvedComposeResourceLimits | ComposeResourceLimits,
70+
options: TemplateRenderOptions = defaultTemplateRenderOptions
5571
): ReadonlyArray<FileSpec> => {
56-
const maybePlaywrightFiles = config.enableMcpPlaywright
57-
? ([
58-
{ _tag: "File", relativePath: "Dockerfile.browser", contents: renderPlaywrightBrowserDockerfile() },
59-
{
60-
_tag: "File",
61-
relativePath: "docker-git-cdp-guard",
62-
contents: renderPlaywrightCdpGuard(),
63-
mode: 0o755
64-
},
65-
{
66-
_tag: "File",
67-
relativePath: "mcp-playwright-start-extra.sh",
68-
contents: renderPlaywrightStartExtra(),
69-
mode: 0o755
70-
},
71-
{
72-
_tag: "File",
73-
relativePath: "docker-git-browser-runtime.sh",
74-
contents: renderPlaywrightBrowserRuntime(),
75-
mode: 0o755
76-
}
77-
] satisfies ReadonlyArray<FileSpec>)
78-
: ([] satisfies ReadonlyArray<FileSpec>)
72+
// Old separate browser files removed — unified browser is provided by Rust module
73+
// (started via background task in entrypoint.sh).
74+
// No more duplication with packages/browser-connection or playwright-browser TS.
75+
const maybePlaywrightFiles: ReadonlyArray<FileSpec> = []
7976

8077
return [
8178
{ _tag: "File", relativePath: "Dockerfile", contents: renderDockerfile(config) },
8279
{ _tag: "File", relativePath: "entrypoint.sh", contents: renderEntrypoint(config), mode: 0o755 },
8380
{
8481
_tag: "File",
8582
relativePath: "docker-compose.yml",
86-
contents: renderDockerCompose(config, composeResourceLimits)
83+
contents: renderDockerCompose(config, composeResourceLimits, options.compose)
8784
},
8885
{ _tag: "File", relativePath: ".dockerignore", contents: renderDockerignore() },
8986
{ _tag: "File", relativePath: "docker-git.json", contents: renderConfigJson(config) },

packages/app/src/lib/core/templates/dockerfile.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,32 @@ RUN set -eu; \
6868
openssh-server git gh ca-certificates curl unzip bsdutils sudo tmux \
6969
make docker.io docker-compose-v2 bash-completion zsh zsh-autosuggestions xauth \
7070
ncurses-term jq \
71-
&& rm -rf /var/lib/apt/lists/*
71+
rustc cargo \
72+
&& rm -rf /var/lib/apt/lists/*
73+
74+
ENV PATH="/root/.cargo/bin:/usr/local/cargo/bin:\${PATH}"
75+
76+
# CHANGE: install the unified Rust browser connection with a current Rust toolchain.
77+
# WHY: rust-browser-connection uses a Cargo.lock format newer than Ubuntu apt cargo; silently ignoring install failures leaves MCP Playwright without the noVNC/CDP browser module.
78+
# QUOTE(ТЗ): "добиться что бы всё работало и этому были доказательства"
79+
# REF: issue-347
80+
# SOURCE: n/a
81+
# FORMAT THEOREM: image_build_success -> executable(docker-git-browser-connection) on PATH
82+
# PURITY: SHELL
83+
# EFFECT: Docker build downloads rustup and installs the GitHub Rust crate.
84+
# INVARIANT: generated project images fail fast instead of booting with MCP_PLAYWRIGHT_ENABLE=1 and no browser binary.
85+
# COMPLEXITY: O(network + cargo_build)
86+
RUN set -eu; \
87+
curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs -o /tmp/rustup-init.sh; \
88+
sh /tmp/rustup-init.sh -y --profile minimal --default-toolchain stable; \
89+
rm -f /tmp/rustup-init.sh; \
90+
rustc --version; \
91+
cargo --version
92+
93+
# Install unified Rust browser connection (noVNC + CDP + single dg-*-browser guarantee)
94+
# Replaces all previous TS/MCP browser-connection duplication (per issue #347)
95+
RUN cargo install --git https://github.com/ProverCoderAI/rust-browser-connection --branch main docker-git-browser-connection --locked \
96+
&& docker-git-browser-connection --version
7297
7398
# Passwordless sudo for all users (container is disposable)
7499
RUN printf "%s\\n" "ALL ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/zz-all \
@@ -198,8 +223,9 @@ if [[ "$MCP_PLAYWRIGHT_CDP_GUARD" == "1" ]]; then
198223
exec playwright-mcp --cdp-endpoint "$CDP_ENDPOINT" --cdp-timeout "$MCP_PLAYWRIGHT_CDP_TIMEOUT" "\${EXTRA_ARGS[@]}" "$@"
199224
fi
200225
201-
# kechangdev/browser-vnc binds Chromium CDP on 127.0.0.1:9222; it also host-checks HTTP requests.
202-
# When the guard is disabled, preserve the old behavior by converting the HTTP endpoint to WS.
226+
# Unified Rust browser (docker-git-browser-connection) now provides the single
227+
# dg-$PROJECT-browser container with CDP on :9223 (reachable by name when --network is passed).
228+
# MCP Playwright connects directly to ws://dg-...-browser:9223 — no more separate browser-vnc or cdp-guard duplication (per #347).
203229
fetch_cdp_version() {
204230
curl -sSf --connect-timeout 3 --max-time 10 -H 'Host: 127.0.0.1:9222' "\${CDP_ENDPOINT%/}/json/version" 2>/dev/null
205231
}
@@ -236,9 +262,9 @@ RUN chmod +x /usr/local/bin/docker-git-playwright-mcp`
236262

237263
const renderDockerfilePlaywrightRuntime = (config: TemplateConfig): string =>
238264
config.enableMcpPlaywright
239-
? `# docker-git nested Playwright browser runtime context
240-
COPY Dockerfile.browser docker-git-cdp-guard mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/
241-
RUN chmod +x /opt/docker-git/browser/docker-git-cdp-guard /opt/docker-git/browser/mcp-playwright-start-extra.sh /opt/docker-git/browser/docker-git-browser-runtime.sh`
265+
? `# Unified Rust browser (dg-*-browser) is started by docker-git-browser-connection binary
266+
# No more COPY of separate browser files — single session guaranteed by Rust module (see entrypoint and rust-browser-connection repo)
267+
# Old browser-vnc + cdp-guard duplication removed per #347`
242268
: ""
243269

244270
/**

0 commit comments

Comments
 (0)