Skip to content

Commit d41689c

Browse files
author
bcode
committed
sync: harness 04f7716
7 upstream commits since fefca43. Windows fixes (#232, #240) + skill rename (#242). All in the protected src/browser_harness/ zone — taken verbatim. No divergences touched. Files: - src/browser_harness/_ipc.py: BH_TMP_DIR override; drop DETACHED_PROCESS - src/browser_harness/admin.py: ensure_daemon warm probe via ipc.connect - src/browser_harness/helpers.py: screenshot + debug-click via ipc._TMP - SKILL.md: name: browser-harness -> browser - install.md: name: browser-harness-install -> browser-install
1 parent 1f64e1c commit d41689c

6 files changed

Lines changed: 25 additions & 21 deletions

File tree

UPSTREAM.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Each upstream has its own append-only table. Add a row every time you pull.
8484
|---|---|---|---|---|
8585
| 2026-04-26 | — (initial) | `216a2c9` | bcode | Initial vendor at A2. Verbatim copy of `browser-use/browser-harness@216a2c9`. No divergences yet. |
8686
| 2026-04-28 | `216a2c9` | `fefca43` | bcode | 41 upstream commits. **Major restructure** (PR #229): src-layout reorg (`*.py``src/browser_harness/*.py`), `domain-skills/``agent-workspace/domain-skills/`, agent-editable surface moved from root `helpers.py` to `agent-workspace/agent_helpers.py`, new `_ipc.py` for Windows TCP / POSIX AF_UNIX support, tests moved to `tests/{unit,integration}/`. Also: Expedia/Substack/Loom/Gmail domain skills, screenshot max-dim, helpers.switch_tab dict-accept, websockets pin 15.0.1, BU_CDP_URL, doctor improvements, JS eval refactor. Adapted our integration: `browser-execute.ts` invokes `browser-harness` console-script (not `python run.py`); `harness.ts` `PRESERVED_PATHS` updated to `agent-workspace/agent_helpers.py`; smoke test now imports from `browser_harness` package; `browser-execute.txt` prompt updated to point at new helper paths. Divergences touched: none (still just `.gitignore` + `.venv/`). |
87+
| 2026-04-28 | `fefca43` | `04f7716` | bcode | 7 upstream commits. Windows fixes (PRs #232, #240) + skill rename (PR #242). Files: `src/browser_harness/_ipc.py` (BH_TMP_DIR override for sock/port/pid/log/screenshot dir; drop DETACHED_PROCESS to suppress empty Windows console window), `src/browser_harness/admin.py` (route `ensure_daemon` warm probe through `ipc.connect` so Windows TCP loopback works; new `_open_inspect=False` flag on `ensure_daemon` used by `run_setup` to prevent chrome://inspect tab flooding; drop unused `_paths()` helper), `src/browser_harness/helpers.py` (`capture_screenshot` and click-debug overlay route through `ipc._TMP` instead of `tempfile.gettempdir()` so BH_TMP_DIR covers them too), `SKILL.md` (`name: browser-harness` → `name: browser`), `install.md` (`name: browser-harness-install` → `name: browser-install`). All in protected `src/browser_harness/*.py` zone — taken verbatim. SKILL/install frontmatter rename only affects how end-users invoke the skill (`/browser` vs `/browser-harness`); our `browser-execute.txt` references SKILL.md by file path, so no integration code changes. Divergences touched: none. PR #240 e2e tested separately on Linux against headless Chrome before sync. |
8788

8889
---
8990

packages/bcode-browser/harness/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: browser-harness
2+
name: browser
33
description: Direct browser control via CDP. Use when the user wants to automate, scrape, test, or interact with web pages. Connects to the user's already-running Chrome.
44
---
55

packages/bcode-browser/harness/install.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: browser-harness-install
2+
name: browser-install
33
description: Install and bootstrap browser-harness into the current agent, then connect it to the user's real Chrome with minimal prompting.
44
---
55

packages/bcode-browser/harness/src/browser_harness/_ipc.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
from pathlib import Path
44

55
IS_WINDOWS = sys.platform == "win32"
6-
# POSIX: /tmp keeps AF_UNIX paths under sun_path limits (104 on macOS, 108 on Linux).
7-
# tempfile.gettempdir() on macOS returns /var/folders/... (~49 chars) which combined with
8-
# a 64-char BU_NAME exceeds the limit. Windows uses TCP, so any tempdir is fine.
9-
_TMP = Path(tempfile.gettempdir()) if IS_WINDOWS else Path("/tmp")
6+
# Override via BH_TMP_DIR for sock/port/pid/log + screenshot output (e.g. per-session
7+
# scratch dir). Default keeps AF_UNIX paths under sun_path limits (104 macOS, 108 Linux):
8+
# /tmp on POSIX (gettempdir() returns long /var/folders/... on macOS); tempdir on Windows.
9+
# Caller picking BH_TMP_DIR is responsible for keeping <dir>/bu-<NAME>.sock under 104 chars.
10+
_TMP = Path(os.environ.get("BH_TMP_DIR") or (tempfile.gettempdir() if IS_WINDOWS else "/tmp"))
1011
_NAME_RE = re.compile(r"\A[A-Za-z0-9_-]{1,64}\Z")
1112

1213

@@ -30,7 +31,12 @@ def sock_addr(name): # display-only, used in log lines
3031

3132
def spawn_kwargs(): # subprocess.Popen flags so the daemon detaches from this terminal
3233
if IS_WINDOWS:
33-
return {"creationflags": subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP}
34+
# CREATE_NO_WINDOW: no console window for the daemon. CREATE_NEW_PROCESS_GROUP:
35+
# daemon doesn't receive Ctrl-C/Ctrl-Break sent to the parent terminal, so
36+
# closing that terminal doesn't kill it. DETACHED_PROCESS is intentionally
37+
# omitted: per Win32 docs it overrides CREATE_NO_WINDOW, causing Windows to
38+
# allocate a fresh console for the (still console-subsystem) python.exe.
39+
return {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW}
3440
return {"start_new_session": True}
3541

3642

packages/bcode-browser/harness/src/browser_harness/admin.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ def _load_env_file(p):
3737
DOCTOR_TEXT_LIMIT = 140
3838

3939

40-
def _paths(name):
41-
n = name or NAME
42-
return ipc.sock_addr(n), str(ipc.pid_path(n))
43-
44-
4540
def _log_tail(name):
4641
try:
4742
return ipc.log_path(name or NAME).read_text().strip().splitlines()[-1]
@@ -143,14 +138,15 @@ def _doctor_short_text(value, limit=None):
143138
return value if len(value) <= limit else value[:limit - 3] + "..."
144139

145140

146-
def ensure_daemon(wait=60.0, name=None, env=None):
141+
def ensure_daemon(wait=60.0, name=None, env=None, _open_inspect=True):
147142
"""Idempotent. Self-heals stale daemon, cold Chrome, and missing Allow on chrome://inspect."""
148143
if daemon_alive(name):
149144
# Stale daemons accept connects AND reply to meta:* (pure Python) even when the
150145
# CDP WS to Chrome is dead — probe with a real CDP call and require "result".
146+
# Must go through ipc.connect so this works on Windows (TCP loopback) too;
147+
# raw AF_UNIX here would fail on every warm call and churn the daemon.
151148
try:
152-
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); s.settimeout(3)
153-
s.connect(_paths(name)[0])
149+
s = ipc.connect(name or NAME, timeout=3.0)
154150
s.sendall(b'{"method":"Target.getTargets","params":{}}\n')
155151
data = b""
156152
while not data.endswith(b"\n"):
@@ -176,7 +172,8 @@ def ensure_daemon(wait=60.0, name=None, env=None):
176172
time.sleep(0.2)
177173
msg = _log_tail(name) or ""
178174
if local and attempt == 0 and _needs_chrome_remote_debugging_prompt(msg):
179-
_open_chrome_inspect()
175+
if _open_inspect:
176+
_open_chrome_inspect()
180177
print("browser-harness: click Allow on chrome://inspect (and tick the checkbox if shown)", file=sys.stderr)
181178
restart_daemon(name)
182179
continue
@@ -205,7 +202,7 @@ def restart_daemon(name=None):
205202
ensure_daemon(). The function itself only stops."""
206203
import signal
207204

208-
_, pid_path = _paths(name)
205+
pid_path = str(ipc.pid_path(name or NAME))
209206
try:
210207
c = ipc.connect(name or NAME, timeout=5.0)
211208
c.sendall(b'{"meta":"shutdown"}\n')
@@ -574,7 +571,7 @@ def run_setup():
574571
last = first_err
575572
while time.time() < deadline:
576573
try:
577-
ensure_daemon(wait=5.0)
574+
ensure_daemon(wait=5.0, _open_inspect=False)
578575
print("daemon is up.")
579576
return 0
580577
except RuntimeError as e:

packages/bcode-browser/harness/src/browser_harness/helpers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Core helpers live here. Agent-editable helpers live in
44
BH_AGENT_WORKSPACE/agent_helpers.py.
55
"""
6-
import base64, importlib.util, json, math, os, tempfile, time, urllib.request
6+
import base64, importlib.util, json, math, os, time, urllib.request
77
from pathlib import Path
88
from urllib.parse import urlparse
99

@@ -186,7 +186,7 @@ def click_at_xy(x, y, button="left", clicks=1):
186186
try:
187187
from PIL import Image, ImageDraw
188188
dpr = js("window.devicePixelRatio") or 1
189-
path = capture_screenshot(str(Path(tempfile.gettempdir()) / f"debug_click_{_debug_click_counter}.png"))
189+
path = capture_screenshot(str(ipc._TMP / f"debug_click_{_debug_click_counter}.png"))
190190
img = Image.open(path)
191191
draw = ImageDraw.Draw(img)
192192
px, py = int(x * dpr), int(y * dpr)
@@ -232,7 +232,7 @@ def scroll(x, y, dy=-300, dx=0):
232232
def capture_screenshot(path=None, full=False, max_dim=None):
233233
"""Save a PNG of the current viewport. Set max_dim=1800 on a 2× display to
234234
keep the file under the 2000px-per-side limit some image-aware LLMs enforce."""
235-
path = path or str(Path(tempfile.gettempdir()) / "shot.png")
235+
path = path or str(ipc._TMP / "shot.png")
236236
r = cdp("Page.captureScreenshot", format="png", captureBeyondViewport=full)
237237
open(path, "wb").write(base64.b64decode(r["data"]))
238238
if max_dim:

0 commit comments

Comments
 (0)