Skip to content

Commit 50cbcee

Browse files
authored
Merge pull request #22 from browser-use/feat/per-session-bh-tmp-dir
feat(browser-execute): per-session BH_TMP_DIR scratch dir
2 parents a59af90 + a088c85 commit 50cbcee

3 files changed

Lines changed: 34 additions & 2 deletions

File tree

packages/bcode-browser/src/browser-execute.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,30 @@
1313
// pipe stdout+stderr back. BU_NAME is namespaced by sessionID so parallel
1414
// sub-agents (each with their own session) get isolated daemons + browsers.
1515
//
16+
// BH_TMP_DIR points at a per-session scratch dir so sock/port/pid/log + screenshot
17+
// output land somewhere predictable per session, instead of all sessions sharing
18+
// /tmp. The Level-2 wrapper supplies the cache root; we own the layout convention.
19+
//
1620
// Level 1 per decisions.md §1c — substantial implementation lives here. The
1721
// Level-2 hook in packages/opencode is a one-line wrapper.
1822

23+
import fs from "fs/promises"
24+
import path from "path"
1925
import { Effect, Stream } from "effect"
2026
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
2127
import z from "zod"
2228
import { resolveHarnessDir } from "./harness"
2329
import { uvLocate } from "./uv-locate"
2430

31+
// Canonical per-session scratch dir layout. Caller supplies dataDir
32+
// (e.g. opencode's Global.Path.data); we own the `sessions/<id>` shape.
33+
// AF_UNIX sun_path is 104 bytes on macOS — `<dataDir>/sessions/<sessionID>/bu-<sessionID>.sock`
34+
// must fit. SessionID is `ses_` + 26 chars (30 chars). The literal suffix is
35+
// `/sessions/` (10) + 30 + `/bu-` (4) + 30 + `.sock` (5) = 79 chars, leaving
36+
// 25 chars of headroom for dataDir. Typical XDG dataDir is well under that.
37+
export const sessionScratchDir = (dataDir: string, sessionID: string) =>
38+
path.join(dataDir, "sessions", sessionID)
39+
2540
const DEFAULT_TIMEOUT_MS = 60 * 1000
2641
const MAX_TIMEOUT_MS = 10 * 60 * 1000
2742

@@ -37,6 +52,11 @@ export type Parameters = z.infer<typeof parameters>
3752

3853
export interface ExecuteContext {
3954
readonly sessionID: string
55+
// Per-session scratch dir, passed to the harness as BH_TMP_DIR. The harness
56+
// mkdirs it on import, but we mkdir-p here too so failures surface as a
57+
// direct effect error rather than a child-process exit. Pre-compute via
58+
// sessionScratchDir(dataDir, sessionID).
59+
readonly bhTmpDir: string
4060
// Optional progress callback invoked per output chunk (combined stdout+stderr).
4161
// Level-2 supplies this to drive TUI streaming via opencode's `ctx.metadata`.
4262
// The callback receives the fully accumulated output so far, not just the
@@ -72,14 +92,15 @@ export const make = Effect.fn("BrowserExecute.make")(function* () {
7292
const execute = (args: Parameters, ctx: ExecuteContext) =>
7393
Effect.gen(function* () {
7494
const harnessDir = yield* Effect.promise(() => resolveHarnessDir())
95+
yield* Effect.promise(() => fs.mkdir(ctx.bhTmpDir, { recursive: true }))
7596
const uv = yield* locate
7697
const proc = ChildProcess.make(
7798
uv,
7899
["run", "--project", harnessDir, "browser-harness", "-c", args.python],
79100
{
80101
cwd: harnessDir,
81102
extendEnv: true,
82-
env: { BU_NAME: ctx.sessionID },
103+
env: { BU_NAME: ctx.sessionID, BH_TMP_DIR: ctx.bhTmpDir },
83104
stdin: "ignore",
84105
},
85106
)

packages/opencode/src/agent/agent.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ export const layer = Layer.effect(
8282
Effect.fn("Agent.state")(function* (_ctx) {
8383
const cfg = yield* config.get()
8484
const skillDirs = yield* skill.dirs()
85-
const whitelistedDirs = [Truncate.GLOB, ...skillDirs.map((dir) => path.join(dir, "*"))]
85+
// browser_execute writes per-session screenshots/logs under
86+
// <Global.Path.data>/sessions/<sessionID>. Whitelist the parent so
87+
// the agent can read its own screenshots back without permission prompts.
88+
const browserSessionsGlob = path.join(Global.Path.data, "sessions", "*")
89+
const whitelistedDirs = [Truncate.GLOB, browserSessionsGlob, ...skillDirs.map((dir) => path.join(dir, "*"))]
8690

8791
const defaults = Permission.fromConfig({
8892
"*": "allow",

packages/opencode/src/tool/browser-execute.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import { Effect } from "effect"
66
import type z from "zod"
77
import { BrowserExecute } from "@browser-use/bcode-browser/browser-execute"
8+
import { Global } from "@/global"
89
import * as Tool from "./tool"
910
import DESCRIPTION from "./browser-execute.txt"
1011

@@ -33,6 +34,12 @@ export const BrowserExecuteTool = Tool.define(
3334

3435
const result = yield* impl.execute(args, {
3536
sessionID: ctx.sessionID,
37+
// Per-session scratch under Global.Path.data (not cache — survives
38+
// CACHE_VERSION wipes). Harness writes sock/port/pid/log + screenshots
39+
// here. Agent reads screenshots back via the read tool; the agent
40+
// permission ruleset (agent.ts) allows <Global.Path.data>/sessions/*
41+
// so that read doesn't prompt.
42+
bhTmpDir: BrowserExecute.sessionScratchDir(Global.Path.data, ctx.sessionID),
3643
// Stream chunks to the TUI as they arrive — same pattern as bash.
3744
onChunk: (output) =>
3845
ctx.metadata({

0 commit comments

Comments
 (0)