feat(cli): optional OpenShell sandbox backend for the bridge daemon - #296
Conversation
Relayed sandbox ops (`sandbox_provider: "subprocess"`) can now execute inside an OpenShell sandbox on the paired machine instead of `child_process` on the host. Same relay protocol, isolated execution, gateway-enforced egress — but an empty box: none of the user's repos, toolchains, or CLI auth are visible. So it is explicit opt-in only, via `oma bridge setup` (offered when a gateway is detected) or `BRIDGE_SANDBOX_BACKEND=openshell`. Deliberately no "endpoint present → use it" auto-detect, unlike the in-cluster k8s-bridge: silently flipping a laptop would break every agent that relies on the host. - extract a RelaySandboxExecutor seam with all 8 relay ops; subprocess stays the default and its behavior is unchanged - vendor a lean OpenShell gRPC client (the CLI ships a zero-runtime-dep esbuild bundle and can't resolve the private @duyet/oma-sandbox), with a test asserting the proto stays byte-identical to the adapter's - destroyAll now awaits box teardown under a bounded timeout - `oma bridge status` reports the active backend + gateway reachability
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Reviewer's GuideIntroduce an optional OpenShell-based sandbox backend for the bridge daemon, refactoring sandbox execution behind a pluggable executor interface, adding a vendored OpenShell gRPC client, explicit backend selection semantics, user prompts and settings persistence, daemon/status wiring, and tests/docs to cover the behavior and security trade-offs. Sequence diagram for sandbox backend selection and OpenShell opt-insequenceDiagram
actor User
participant SetupCmd as bridge_setup
participant Config as config_settings
participant Daemon as bridge_daemon
participant BackendSel as resolveSandboxBackend
participant SandboxMgr as BridgeSandboxManager
participant OSClient as OpenShellClient
User->>SetupCmd: runSetupInner
SetupCmd->>Config: readSettings
SetupCmd->>SetupCmd: probeOpenShellGateway
SetupCmd->>User: offerOpenShellBackend prompt
User-->>SetupCmd: choose subprocess or openshell
SetupCmd->>Config: writeSettings(sandboxBackend, openshellEndpoint)
User->>Daemon: runDaemon
Daemon->>Config: readSettings
Daemon->>BackendSel: resolveSandboxBackend(env, settings)
BackendSel-->>Daemon: { kind, endpoint, reason }
Daemon->>SandboxMgr: new BridgeSandboxManager(backend?)
alt kind == openshell
Daemon->>OSClient: createOpenShellBackend(endpoint, token, image, tls)
else kind == subprocess
Daemon->>SandboxMgr: createSubprocessBackend
end
Daemon->>Daemon: log sandbox backend + reason
Daemon->>SandboxMgr: handle(exec/readFile/...) using selected backend
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Review limit reached
Next review available in: 52 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (13)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
openshell-client.ts,buildCredentialsuses a dynamicrequire("node:fs")inside an otherwise ESM-style module; consider switching to a regularimport/readFileSyncso the CLI bundle doesn’t rely onrequireat runtime, which can be brittle with esbuild andtype: module.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `openshell-client.ts`, `buildCredentials` uses a dynamic `require("node:fs")` inside an otherwise ESM-style module; consider switching to a regular `import`/`readFileSync` so the CLI bundle doesn’t rely on `require` at runtime, which can be brittle with esbuild and `type: module`.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
* feat(cli): optional OpenShell sandbox backend for the bridge daemon Relayed sandbox ops (`sandbox_provider: "subprocess"`) can now execute inside an OpenShell sandbox on the paired machine instead of `child_process` on the host. Same relay protocol, isolated execution, gateway-enforced egress — but an empty box: none of the user's repos, toolchains, or CLI auth are visible. So it is explicit opt-in only, via `oma bridge setup` (offered when a gateway is detected) or `BRIDGE_SANDBOX_BACKEND=openshell`. Deliberately no "endpoint present → use it" auto-detect, unlike the in-cluster k8s-bridge: silently flipping a laptop would break every agent that relies on the host. - extract a RelaySandboxExecutor seam with all 8 relay ops; subprocess stays the default and its behavior is unchanged - vendor a lean OpenShell gRPC client (the CLI ships a zero-runtime-dep esbuild bundle and can't resolve the private @duyet/oma-sandbox), with a test asserting the proto stays byte-identical to the adapter's - destroyAll now awaits box teardown under a bounded timeout - `oma bridge status` reports the active backend + gateway reachability * fix(web): stop underlining nav and footer links Footer columns and header menus are marked up as <ul><li><a>, so the `li a` half of the prose link selector was matching them and applying body-copy underlines to navigation. Opt nav/footer back out; those links already signal hover with a color transition. Also adds a draft blog post covering OpenShell sandbox support across all four deployment shapes (self-host gRPC, bridge daemon, Cloudflare, Kubernetes via Helm), with step-by-step setup and the limitations of each. Kept as a draft until #295 and #296 merge — two of its four paths ship in those PRs. * style(web): restyle landing to AnyRouter design language Warm-orange gateway brand family (AA-checked light/dark), blueprint dotted-grid + corner-bracket viz utilities, and editorial ordinal section markers. Hero HowItFits diagram preserved; only its framing harmonized. * style(web): unify landing on shadcn card + radius scale shadcn New-York elevation on section cards, unified radius scale, and both landing diagrams wrapped in the shared blueprint measurement-grid plate. Hero diagram content preserved. * style(web): animate landing flow diagram wires Packet-dot motion streaming along the input→agent→result connectors (AnyRouter gateway-flow style), gated behind prefers-reduced-motion. * feat(web): add Anatomy blueprint section to landing New 'What one request touches' section drawing OMA's real request lifecycle (Session/event-log DO -> Harness -> Sandbox -> Vault proxy) as an animated schematic; adds reusable .bp-wire / .arch-node vocab. Sections renumbered 01-04. * feat(web): add Reach provider fan-out diagram section New 'Any model, any sandbox' section: animated SVG fan schematic (providers fan in -> control plane -> sandboxes fan out), offset-path packet motion, reduced-motion gated. Sections renumbered 01-05. * feat(sandbox): browser-vm — run the agent sandbox as a WASM VM in a browser tab A new relay-shaped sandbox provider whose runtime lives inside a user's browser tab (v86 WASM VM by default) instead of a server-side container. Reuses the RuntimeRoom WebSocket relay: the tab registers as a runtime (kind=browser-vm) and services sandbox.op frames against the VM. - packages/sandbox: BrowserVmSandbox adapter + wire protocol, provider seeding, classify branch (bridge/browser-vm). - apps/agent: BrowserVmRelaySandbox (lazy RuntimeRoom sandbox-WS attach, kind-aware pickOnlineRuntimeId); resolveCfSandbox branches bridge by type so browser-vm no longer falls through to the laptop-daemon relay. - apps/main: GET /sandbox-tab host page (COOP/COEP, v86 engine seam, OPFS workspace mirror, pairing + 25s heartbeat); _attach accepts ?access_token (browser WS can't set Authorization); /exchange + runtimes gain a kind discriminator. - runtimes.kind migrations (main + main-node pg/sqlite). - apps/console: RuntimesList browser-vm card with Open sandbox tab action. - kind-aware /v1/hosting_types health; main-node reports not_configured. - docs: AGENTS.md provider row + docs/browser-vm-sandbox.md. Engines are BYO-license (v86 BSD-2 default; WebContainers/CheerpX slots). Deferred: tab vault MITM, workspace backups, chunk streaming, mounts. * chore(web): add shadcn components.json + @/* path alias Enables future shadcn component adds; @/* -> src/* alias. * fix(sandbox): browser-vm UTF-8-safe serial decode + exec timeout slack - Host page v86 readFile decoded with bare atob() while writeFile encoded UTF-8-safely — non-ASCII file reads and all exec stdout/stderr (collected via readFile) mojibaked. Symmetric decode now. - Adapter armed its pending-call timer at exactly the command budget, but the host needs that budget plus two post-exec output round trips; near-timeout commands failed client-side. #call gets timeoutMs + 15s slack (mirrors bridge-relay); wire timeout_seconds unchanged. * docs(web): present the full sandbox-runtime matrix incl. browser-vm - landing: browser-vm card in SandboxProviderGrid + infrastructure page, CF-providers FAQ answer, homepage SEO description - fit-diagram: browser-vm app-window ProviderMark (shared with Console) - blog: draft post 'Your Browser Tab Is Now an Agent Sandbox' - docs + docs site: browser-vm rows in every sandbox-provider table (runtimes, features, self-host, how-it-works, architecture, node-docker)
Track A of the OpenShell CLI/k8s plan. Lets
oma bridge daemonexecute relayed sandbox ops inside an OpenShell sandbox on the user's own machine instead of raw host subprocess. Charts (Track B) are a separate PR.Security posture — the actual trade-off
/bin/shon the host FSgh/cloud authNeither is strictly better. Isolation is exactly what makes the box useless for the workflow most people paired their machine for. So the user picks.
Why explicit opt-in, with no auto-detect
resolveBridgeBackendKind(apps/k8s-bridge) flips to OpenShell as soon asOPENSHELL_GATEWAY_ENDPOINTis present. That's safe for a dedicated in-cluster deployment and unsafe on a laptop: someone who installed OpenShell for unrelated reasons would silently lose every agent's view of their files. So selection here is daemon settings →BRIDGE_SANDBOX_BACKEND→ subprocess, and nothing else.bridge setupoffers the switch when it probes a live gateway, states the trade-off in the prompt, and persists whichever way the user answers so it never re-asks. The divergence is called out in a code comment and covered by a test.What changed
bridge-sandbox.ts— extracted aRelaySandboxExecutorseam carrying all 8 relay ops (exec / readFile / readFileBytes / writeFile / writeFileBytes / setEnvVars / ping / destroy). Deliberately not the k8s-bridge's 5-opBoxExecutor, which lacks the bytes ops and would silently break binary file relay. Subprocess behavior andBridgeSandboxManager's public surface are unchanged;bridge-sandbox.test.tspasses untouched.openshell-client.ts— vendored lean gRPC client + gateway probe. The CLI publishes a single zero-runtime-dependency esbuild bundle and the internal@duyet/oma-*packages are private, so importing the real adapter is impossible; it also pulls observability at module scope.@grpc/*are devDeps (bundled; 1.0MB → 2.1MB). A test asserts the proto string stays byte-identical topackages/sandbox/src/adapters/openshell.ts— field-number drift is the failure mode that would silently corrupt the wire format./workspacepath rewriting on the openshell backend (/workspaceis a real container path there), and exec output passes through verbatim — theexit=Nshape is what boxrun/k8s-bridge consumers already parse.?? 600default is 600ms, an upstream typo. Relay timeout, else 120s.destroyAll()now awaits the destroys (OpenShell's is an asyncDeleteSandbox) bounded at 10s, so a hung gateway can't wedge shutdown past launchd's ExitTimeOut. Boxes survive a WS reconnect.bridge statusshows the active backend and, for openshell, endpoint + reachability — "configured" and "reachable" are different claims.Stated limitations
createBoxop), somapEnvironmentConfigToOpenShellPolicyhas nothing to feed it. Egress is the gateway's own default policy.Verification
pnpm typecheck— clean.pnpm --filter @getoma/cli test— 8 files, 62 tests passed.pnpm test:packages— all 10 packages green.pnpm test:console— 29 files / 177 tests green.apps/agentintegration tests, which this PR does not touch); they pass when run in isolation.pnpm --filter @getoma/cli buildsucceeds and the built CLI starts.Summary by Sourcery
Introduce an optional OpenShell-backed sandbox substrate for the bridge daemon, with explicit user opt-in and surfaced backend selection, while keeping subprocess as the default.
New Features:
oma bridge status.oma bridge setupto detect a local OpenShell gateway and offer a one-time choice of sandbox backend, persisting the decision in a new daemon settings file.Enhancements:
Build:
Tests: