Skip to content

phase-h: TS harness port (v0.1.0 cut)#41

Open
Alezander9 wants to merge 4 commits intomainfrom
feat/phase-h-ts-harness
Open

phase-h: TS harness port (v0.1.0 cut)#41
Alezander9 wants to merge 4 commits intomainfrom
feat/phase-h-ts-harness

Conversation

@Alezander9
Copy link
Copy Markdown
Member

Phase H — TS harness port (v0.1.0 cut)

Drops uv from the install path and replaces the Python browser-harness subprocess + daemon with an in-process TypeScript CDP layer. Implements the migration plan end-to-end (steps 0–6). Step 7 is "tag from main"; that's an admin call after merge.

Net LOC (hand-written maintenance surface)

Lines
Hand-written code added (TS/TSX/MD/TXT/SH) +2,752
Hand-written code deleted (incl. ~3,400 LOC vendored Python harness) −5,254
Net −2,499

(Excludes generated.ts ~15k and the two protocol JSONs ~1.5 MB, which are committed-data, not maintenance surface — bun src/cdp/gen.ts regenerates generated.ts from the JSONs.)

The plan's target was −1,800 LOC; we beat it because the rewrite was tighter than budgeted (browser-execute.ts: 163 → ~125 LOC; harness.ts + uv-locate.ts: 217 LOC removed entirely).

What's in the box

Step 1 — vendor the CDP layer. packages/bcode-browser/src/cdp/: session.ts (~380 LOC), gen.ts (~270 LOC, bun src/cdp/gen.ts to regenerate), generated.ts (~15k LOC machine-generated, committed), browser_protocol.json + js_protocol.json from chromedevtools/devtools-protocol. Initial copy from browser-use/browser-harness-js@95b7a22a; ours from here on. Provenance recorded in packages/bcode-browser/src/cdp/PROVENANCE.md. The export namespace carve-out in generated.ts is documented in EXCEPTIONS.md (it's regenerated machine output, not hand-authored).

Step 2 — in-process browser_execute.

  • packages/bcode-browser/src/browser-execute.ts (full rewrite). Wraps the snippet with new AsyncFunction("session", code) against a per-opencode-session Session from a process-scope SessionStore. Snippet scope binds only session plus standard JS globals — nothing auto-loaded (Phase H hard rule sync: upstream v1.14.22 (eb7555d3c on dev) #3, "workspace as plain code, no privileged files"). console.log/error/warn/info monkey-patched around each snippet, restored in a finally even on throw/timeout.
  • packages/bcode-browser/src/session-store.ts — per-opencode-session CDP Session map, shared between browser_execute and browser_open_cloud. Optional onEvict cleanup callbacks (cloud-browser stops register here).
  • packages/opencode/src/tool/browser-execute.ts — Level-2 wrapper resolves <projectDir>/.bcode/agent-workspace/ per-call from InstanceState.context (opencode's existing project-detection — same source that finds .bcode/plans, .bcode/db, etc.).
  • packages/opencode/src/tool/browser-execute.txt — new prompt centered on the no-magic / workspace-as-plain-code model. Substitutes {{SKILLS_DIR}} at make-time; workspace path is project-relative (./.bcode/agent-workspace/) and constructible from process.cwd() inside a snippet.
  • agent.ts permission glob: harness/archive entries gone; replaced with **/.bcode/agent-workspace/**/* (read+edit) plus the runtime skills tree at <dataDir>/skills/*.
  • TUI: >>> /... Python-REPL prompts → > / JS prompts; input.pythoninput.code.
  • Deleted harness.ts + uv-locate.ts.

Step 3 — workspace dynamic-import smoke (packages/bcode-browser/test/workspace-import.test.ts). Four cases, all bun test against real fs: import abs path, edit-then-cache-bust, syntax-error rejection, nested workspace paths. The point isn't to build a framework — it's to verify the documented await import("/abs?t=" + Date.now()) pattern is honest about what it claims.

Step 4 — cloud-browser tool (browser_open_cloud).

  • packages/bcode-browser/src/cloud-browser.ts — POST /api/v3/browsers{id, cdpUrl, liveUrl}Session.connect({ wsUrl: cdpUrl })SessionStore.onEvict registers the PATCH stop. Fails fast if BROWSER_USE_API_KEY is absent.
  • packages/opencode/src/tool/browser-open-cloud.{ts,txt} + registry hook (one line).
  • After browser_open_cloud, every subsequent browser_execute drives the cloud browser via the same Session (no per-snippet re-attach).

Step 5 — skills, BROWSER.md, embed.

  • packages/bcode-browser/skills/BROWSER.md — browsercode-owned prompt for the agent's first browser_execute call. Covers connect, target attach, common moves (Page.navigate, mouse, Input.insertText, screenshot), the workspace write-once-import-many pattern, and a guardrails block (no top-level import, no CPU-bound loops without await, JSON-serializable returns).
  • packages/bcode-browser/skills/interaction-skills/*.md — 16 UI-mechanic recipes (dialogs, dropdowns, iframes, shadow DOM, uploads, screenshots, …) initial-copied from browser-harness-js@95b7a22a. Read-only docs; the agent reads them with read, never imports.
  • packages/bcode-browser/script/embed-skills.ts (new) replaces embed-harness.ts in packages/opencode/script/build.ts. Glob-driven, content-hash buildHash sentinel. Compiled binary extracts to <dataDir>/skills/ once per buildHash; baseline-overwrite on every launch (no agent-editable surface here — the agent's editable surface is <projectDir>/.bcode/agent-workspace/).
  • packages/bcode-browser/src/skills.ts (new) — runtime resolver. Dev: in-tree path. Compiled: extract on first call, stat-and-skip on warm launch.
  • Deleted packages/bcode-browser/harness/ (entire vendored Python tree), embed-harness.ts, harness-sync.md, script/check-harness-diff.sh. Trimmed script/check-upstream.sh to drop the harness branch. UPSTREAM.md retitled to track only anomalyco/opencode; the old harness sync log is preserved as historical archaeology in §3.
  • New memory file (agent-side): memory/browsercode/harness_watchlist.md records Python-harness behaviors the agent might want to port to TS later, individually and on cadence-of-need (not a sync schedule). Hard rule f4: scope turbo typecheck to browsercode-packages filter #2 mechanism.

Step 6 — cross-platform smoke.

  • Linux x64 (this Sprite, headless Chrome 147): bun build --compilebcode-linux-x64 (148 MB) boots cleanly without uv. Three test files, 9 cases: workspace-import (4) + cdp-smoke (1, attaches to real Chrome, navigates, reads document.title) + browser-execute (4, end-to-end against headless Chrome incl. workspace dynamic-import inside a snippet). All pass.
  • macOS arm64 + Windows x64: pending admin-laptop runs. Recorded in memory/browsercode/phase_h_smoke_log.md on the agent side.
  • Cloud browser: not exercised here. Requires BROWSER_USE_API_KEY and outbound to api.browser-use.com. Defer to admin smoke or Phase D bring-up.

Hard rules honored

  1. No backwards compatibility. v0.1.0 reads its own filesystem only. Teammates rm -rf ~/.local/share/bcode/ ~/.cache/bcode/ before upgrading. No migration code.
  2. We own the harness now. Initial copy from browser-harness-js@95b7a22a is the last "verbatim" event. No sync cadence. Python-harness behaviors of interest get watch-listed and ported individually.
  3. Workspace as plain code, per-project. <projectDir>/.bcode/agent-workspace/ is a flat dir of .ts files the agent owns and edits with the standard read/write/edit tools. Snippet scope binds only session. Reuse = await import("/abs/path?t=" + Date.now()). Same mechanism for a 5-line wrapper and a 500-line scrape script. git clone && bcode shares the agent's accumulated scripts (.bcode/agent-workspace/ is tracked-by-default).
  4. Net code deletion as success criterion. -2,499 LOC hand-written (-1,800 LOC was the target).
  5. Single tool surface stays. browser_execute is unchanged in mental model (write a snippet, drive the browser, save reusable code as .ts files). Only the snippet language (Python → JS) and process model (subprocess → in-process) change.
  6. Cloud is a parallel surface from day one (decisions §1d). browser_open_cloud lands in the same PR as browser_execute.
  7. export namespace carve-out for generated.ts documented in EXCEPTIONS.md.

Verification

  • bun typecheck clean across all browsercode packages (@browser-use/browsercode-core, @browser-use/bcode-browser, @browser-use/bcode-laminar, @opencode-ai/{core,plugin,sdk}).
  • bun test from packages/bcode-browser/: 9/9 pass (4 workspace-import always-on; 5 chrome-gated under BCODE_SMOKE_CHROME=1).
  • Compiled-binary build (bun run --cwd packages/opencode build -- --single): produces bcode-linux-x64, smoke-test --version passes.

Release-notes draft (for the v0.1.0 tag)

Breaking: TS harness port. v0.1.0 has no uv requirement and is fully incompatible with v0.0.x state.

The browser harness moved from Python-via-uv-run subprocess to in-process TypeScript. browser_execute now takes JS, not Python. Reusable code lives per-project at <projectDir>/.bcode/agent-workspace/ as plain .ts files; the agent imports them with await import("/abs/path?t=" + Date.now()) (no auto-loaded helpers file). git clone && cd && bcode shares the agent's scripts with the project. Cloud-browser attach is a new tool gated on BROWSER_USE_API_KEY.

Before upgrading: rm -rf ~/.local/share/bcode/ ~/.cache/bcode/. No migration path.

Gains: no uv install, single-process, faster cold starts, instant cancellation on yield-point, cleaner TUI streaming, native Windows path support (no AF_UNIX sun_path budget hacks).

Review notes

  • The migration plan suggested two natural review seams (after step 1, after step 4). I landed it as one PR because the seams cross-cut a lot of files and review is easier with the whole shape visible. Happy to split into stacked PRs if the diff size is unwelcome — git rebase -i to break out the four commits already on the branch (9811ba170 step 1+2, 13d028a94 step 3+4, 8875a7c33 step 5, d5ddc7880 step 6).
  • One judgement call worth flagging: opencode has no clean "session-end" hook in the tool layer today, so SessionStore.evict is wired but not called from outside. Cloud browsers stay running until the bcode process exits (which then closes the WS, which the BU side handles). This matches today's uv run subprocess shape (a stuck Python interpreter also outlives the bcode session). Not a regression; documented in cloud-browser.ts.
  • The smoke matrix's macOS / Windows cells are pending admin-laptop runs — the Sprite can't reach those targets. Linux x64 is green end-to-end.

bcode added 4 commits May 7, 2026 23:31
Step 1 — vendor browser-harness-js@95b7a22a CDP layer into
packages/bcode-browser/src/cdp/ (session.ts, gen.ts, generated.ts,
browser_protocol.json, js_protocol.json) + PROVENANCE.md. Initial copy
only; subsequent edits diverge by design (Phase H hard rule #2 — no
sync cadence, we own this from here on).

Step 2 — rewrite packages/bcode-browser/src/browser-execute.ts to evaluate
JS in-process via new AsyncFunction("session", code) against a per-
opencode-session CDP Session singleton (closed via Effect.addFinalizer).
console.log/error/warn/info monkey-patched around each snippet; restored
in finally even on throw/timeout. Snippet scope binds only `session`
plus standard JS globals — nothing auto-loaded (Phase H hard rule #3:
workspace as plain code, no privileged files).

Level-2 wrapper resolves the per-project workspace dir
<projectDir>/.bcode/agent-workspace/ from InstanceState.context at
execute-time; the impl mkdir's it on first call. Prompt rewritten around
the no-magic model with the write-once-import-many pattern as the first
example.

Permission glob in agent.ts: harnessGlob/harnessArchiveGlob/
harnessArchiveEditDeny removed; replaced with project-relative
**/.bcode/agent-workspace/**/* edit-allow.

TUI rendering: switched from Python REPL prompts (">>> " / "... ") to
JS prompts ("> " / "  "); input.python -> input.code.

Deleted: harness.ts, uv-locate.ts (no subprocess/uv path anymore).

Typecheck clean across all browsercode packages.
Step 3 — packages/bcode-browser/test/workspace-import.test.ts. Four
scenarios verifying the BROWSER.md-documented pattern: import abs path,
edit-then-cache-bust, syntax-error rejection, nested workspace paths.
All four pass with bun test against real fs.

Step 4 — packages/bcode-browser/src/cloud-browser.ts (provisions BU cloud
browser via /api/v3/browsers, connects the per-opencode-session Session
to the cdpUrl, registers stop on session evict). Level-2 wrapper at
packages/opencode/src/tool/browser-open-cloud.{ts,txt} + registry
registration.

Restructured the Session lifecycle as a process-scope SessionStore keyed
on opencode sessionID. Both browser_execute and browser_open_cloud share
the same Session per session — a snippet that follows browser_open_cloud
drives the cloud browser, not a freshly-auto-detected local one. The
store also collects per-session cleanup callbacks (cloud-browser stop
via PATCH /api/v3/browsers/<id>); these run when SessionStore.evict is
called. opencode has no clean session-end hook today so evict is wired
but not yet invoked from outside — known gap matching today's subprocess
shape.

BROWSER_USE_API_KEY required; fail-fast with a one-line error pointing
at https://browser-use.com when absent.
- packages/bcode-browser/skills/ — browsercode-owned. BROWSER.md (the
  agent's prompt for browser_execute, centered on the no-magic /
  workspace-as-plain-code model) + interaction-skills/*.md (verbatim
  initial copy from browser-harness-js@95b7a22a; ours after).
- packages/bcode-browser/script/embed-skills.ts — smaller cousin of the
  retired embed-harness.ts. Walks skills/, emits bcode-skills.gen.ts
  with file map + content-hash buildHash sentinel.
- packages/bcode-browser/src/skills.ts — runtime resolver. Dev: in-tree
  path. Compiled: extracts to <dataDir>/skills/ once per build hash;
  baseline-overwrite (no agent-editable surface; the agent's editable
  surface is per-project <projectDir>/.bcode/agent-workspace/).

- packages/opencode/script/build.ts — swap createEmbeddedHarnessBundle
  for createEmbeddedSkillsBundle; rename bcode-harness.gen.ts ->
  bcode-skills.gen.ts in the Bun.build files map and entrypoints.
- packages/opencode/src/agent/agent.ts — drop harnessGlob/harnessArchive*
  (already dropped in step 2); add browserSkillsGlob pointing at
  Skills.skillsDir(Global.Path.data).
- packages/opencode/src/tool/browser-execute.{ts,txt} — substitute
  {{SKILLS_DIR}} at make-time; prompt points at <skillsDir>/BROWSER.md
  and <skillsDir>/interaction-skills/.

- packages/bcode-browser/{README.md,src/index.ts} — refreshed to reflect
  the new contents; harness column gone.

- DELETE packages/bcode-browser/harness/ entirely (~3400 LOC; the
  largest single deletion of the port). Net hand-written code drop from
  this step alone is ~-2000 LOC even after adding ~600 LOC of TS for
  cdp/, browser-execute.ts, cloud-browser.ts, skills.ts, session-store.ts,
  embed-skills.ts.
- DELETE harness-sync.md (retired with the harness vendoring).
- DELETE script/check-harness-diff.sh; trim harness branch from
  script/check-upstream.sh.
- UPSTREAM.md retitled to track only anomalyco/opencode; old harness
  sync log preserved as historical archaeology.
- memory side: created memory/browsercode/harness_watchlist.md (Phase H
  hard rule #2 mechanism — patterns to track for possible porting later,
  no sync cadence).

Typecheck clean across all browsercode packages. workspace-import
smoke tests still pass.
Two new env-gated test files exercise the in-process CDP stack against
headless Chrome (Chrome 147, via google-chrome --headless=new):

- packages/bcode-browser/test/cdp-smoke.test.ts — connect via
  profileDir, attach a target, Page.enable + navigate +
  waitFor("Page.loadEventFired") + Runtime.evaluate("document.title").
  Verifies the vendored CDP layer (session.ts + generated.ts) drives a
  real browser end-to-end.

- packages/bcode-browser/test/browser-execute.test.ts — exercises the
  full Level-1 execute path: AsyncFunction wrapping, console.log
  capture, return-value serialization, multi-call Session reuse via
  SessionStore (a follow-up snippet on the same sessionID sees the
  Session previously connected), and workspace dynamic-import inside
  a snippet (await import(absPath + "?t=" + Date.now())).

Both gated on BCODE_SMOKE_CHROME=1 + BCODE_SMOKE_PROFILE_DIR. Skipped
otherwise — CI does not yet drive a real browser. All 9 tests
(4 workspace-import + 1 cdp-smoke + 4 browser-execute) pass on
Linux x64.

Compiled binary smoke verified separately: bun build --compile
produces bcode-linux-x64 (148 MB) that boots cleanly, prints --help,
and runs without uv on PATH (the only dependency was the deleted
Python harness).

Cloud-browser tool not exercised here — requires BROWSER_USE_API_KEY
and outbound to api.browser-use.com. Defer to admin smoke or Phase D
bring-up.

Smoke matrix: Linux x64 ✓; macOS arm64 + Windows x64 pending
admin-laptop runs (recorded in memory/browsercode/phase_h_smoke_log.md
on the agent side).
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12 issues found across 96 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/bcode-browser/skills/interaction-skills/print-as-pdf.md">

<violation number="1" location="packages/bcode-browser/skills/interaction-skills/print-as-pdf.md:27">
P2: `headerTemplate`/`footerTemplate` placeholders are documented incorrectly; CDP expects class-based spans, not `{{...}}` variables.</violation>
</file>

<file name="packages/bcode-browser/skills/interaction-skills/iframes.md">

<violation number="1" location="packages/bcode-browser/skills/interaction-skills/iframes.md:69">
P2: `sandbox="allow-same-origin"` is described as a blocker, but it actually enables same-origin DOM access. This guidance can mislead users to debug the wrong cause of `contentDocument` failures.</violation>
</file>

<file name="packages/bcode-browser/skills/interaction-skills/connection.md">

<violation number="1" location="packages/bcode-browser/skills/interaction-skills/connection.md:72">
P2: Handle the empty `tabs` case in the startup sequence before accessing `tabs[0].targetId`, otherwise this snippet can throw on fresh browser starts with no real page targets.</violation>
</file>

<file name="packages/bcode-browser/skills/BROWSER.md">

<violation number="1" location="packages/bcode-browser/skills/BROWSER.md:72">
P3: The docs contradict themselves about workspace layout (`flat directory` vs `any depth/subdir`). Clarify this to avoid users organizing scripts incorrectly.</violation>
</file>

<file name="packages/bcode-browser/skills/interaction-skills/dropdowns.md">

<violation number="1" location="packages/bcode-browser/skills/interaction-skills/dropdowns.md:38">
P2: Guard `textContent` before calling `.trim()` to avoid a runtime TypeError in the dropdown option lookup example.</violation>
</file>

<file name="packages/bcode-browser/skills/interaction-skills/cross-origin-iframes.md">

<violation number="1" location="packages/bcode-browser/skills/interaction-skills/cross-origin-iframes.md:26">
P2: Guard the `find(...)` result before using `iframe.targetId` to avoid a runtime crash when no matching OOPIF target exists yet.</violation>

<violation number="2" location="packages/bcode-browser/skills/interaction-skills/cross-origin-iframes.md:35">
P2: Define how to resolve the parent target before calling `session.use(parentTargetId)`; the current example references an undefined variable.</violation>
</file>

<file name="packages/bcode-browser/skills/interaction-skills/shadow-dom.md">

<violation number="1" location="packages/bcode-browser/skills/interaction-skills/shadow-dom.md:11">
P2: The Shadow DOM guidance documents a non-existent CDP option (`pierceShadow` on `DOM.querySelector`/`DOM.querySelectorAll`). Use `DOM.getDocument(..., pierce: true)` / shadow-root traversal before querying.</violation>
</file>

<file name="packages/opencode/src/tool/browser-open-cloud.txt">

<violation number="1" location="packages/opencode/src/tool/browser-open-cloud.txt:3">
P2: The tool description claims cloud browsers are stopped at bcode session end, but current implementation notes they usually persist until process exit because session eviction is not currently called.</violation>
</file>

<file name="packages/bcode-browser/skills/interaction-skills/dialogs.md">

<violation number="1" location="packages/bcode-browser/skills/interaction-skills/dialogs.md:69">
P2: The `beforeunload` “Option A” snippet has a race: it tries to handle the dialog immediately after navigation without waiting for the dialog-open event, so it can miss late-opening dialogs.</violation>
</file>

<file name="packages/bcode-browser/src/cloud-browser.ts">

<violation number="1" location="packages/bcode-browser/src/cloud-browser.ts:103">
P1: Stop the provisioned cloud browser when `session.connect` fails; otherwise failed attach attempts can leak running cloud browser instances.</violation>
</file>

<file name="packages/bcode-browser/src/browser-execute.ts">

<violation number="1" location="packages/bcode-browser/src/browser-execute.ts:131">
P1: Global `console` monkey-patching is not concurrency-safe. If two `execute` calls overlap (different sessions), they clobber each other's patches — one call's output capture silently breaks. Consider a per-call capture approach, e.g. passing a custom logger object into the snippet as an argument (`log`) instead of mutating the global.</violation>
</file>

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Fix all with cubic

Comment on lines +103 to +107
try: () => session.connect({ wsUrl: cdpUrl }),
catch: (err) => (err instanceof Error ? err : new Error(String(err))),
})
return { id, liveUrl } as const
})
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Stop the provisioned cloud browser when session.connect fails; otherwise failed attach attempts can leak running cloud browser instances.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/cloud-browser.ts, line 103:

<comment>Stop the provisioned cloud browser when `session.connect` fails; otherwise failed attach attempts can leak running cloud browser instances.</comment>

<file context>
@@ -0,0 +1,109 @@
+    Effect.runPromise(stop(id).pipe(Effect.ignore)),
+  )
+  yield* Effect.tryPromise({
+    try: () => session.connect({ wsUrl: cdpUrl }),
+    catch: (err) => (err instanceof Error ? err : new Error(String(err))),
+  })
</file context>
Suggested change
try: () => session.connect({ wsUrl: cdpUrl }),
catch: (err) => (err instanceof Error ? err : new Error(String(err))),
})
return { id, liveUrl } as const
})
try {
yield* Effect.tryPromise({
try: () => session.connect({ wsUrl: cdpUrl }),
catch: (err) => (err instanceof Error ? err : new Error(String(err))),
})
} catch (err) {
yield* stop(id).pipe(Effect.ignore)
throw err
}
Fix with Cubic

output += a.map((x) => (typeof x === "string" ? x : serialize(x))).join(" ") + "\n"
if (ctx.onChunk) Effect.runFork(ctx.onChunk(output))
}
console.log = tee
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Global console monkey-patching is not concurrency-safe. If two execute calls overlap (different sessions), they clobber each other's patches — one call's output capture silently breaks. Consider a per-call capture approach, e.g. passing a custom logger object into the snippet as an argument (log) instead of mutating the global.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/src/browser-execute.ts, line 131:

<comment>Global `console` monkey-patching is not concurrency-safe. If two `execute` calls overlap (different sessions), they clobber each other's patches — one call's output capture silently breaks. Consider a per-call capture approach, e.g. passing a custom logger object into the snippet as an argument (`log`) instead of mutating the global.</comment>

<file context>
@@ -59,96 +54,100 @@ export const parameters = Schema.Struct({
+        output += a.map((x) => (typeof x === "string" ? x : serialize(x))).join(" ") + "\n"
+        if (ctx.onChunk) Effect.runFork(ctx.onChunk(output))
+      }
+      console.log = tee
+      console.error = tee
+      console.warn = tee
</file context>
Fix with Cubic


Options worth knowing:
- `landscape: true` — flip orientation
- `displayHeaderFooter: true` + `headerTemplate` / `footerTemplate` — printed HTML (mustache-style variables: `{{pageNumber}}`, `{{totalPages}}`, `{{title}}`, `{{url}}`)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: headerTemplate/footerTemplate placeholders are documented incorrectly; CDP expects class-based spans, not {{...}} variables.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/skills/interaction-skills/print-as-pdf.md, line 27:

<comment>`headerTemplate`/`footerTemplate` placeholders are documented incorrectly; CDP expects class-based spans, not `{{...}}` variables.</comment>

<file context>
@@ -0,0 +1,69 @@
+
+Options worth knowing:
+- `landscape: true` — flip orientation
+- `displayHeaderFooter: true` + `headerTemplate` / `footerTemplate` — printed HTML (mustache-style variables: `{{pageNumber}}`, `{{totalPages}}`, `{{title}}`, `{{url}}`)
+- `scale: 0.8` — shrink to fit
+- `pageRanges: '1-3,7'` — subset of pages
</file context>
Suggested change
- `displayHeaderFooter: true` + `headerTemplate` / `footerTemplate` — printed HTML (mustache-style variables: `{{pageNumber}}`, `{{totalPages}}`, `{{title}}`, `{{url}}`)
- `displayHeaderFooter: true` + `headerTemplate` / `footerTemplate` — printed HTML (inject values with classes like `<span class="pageNumber"></span>`, `<span class="totalPages"></span>`, `<span class="title"></span>`, `<span class="url"></span>`)
Fix with Cubic


- A frame that was same-origin can become cross-origin after navigation inside it (e.g. OAuth redirect). Re-check with `contentDocument` truthiness.
- `iframe.contentDocument === null` right after insertion — wait for `load` on the iframe before reading.
- CSP `frame-ancestors`/`sandbox="allow-same-origin"` can block `contentDocument` access even when origins match.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: sandbox="allow-same-origin" is described as a blocker, but it actually enables same-origin DOM access. This guidance can mislead users to debug the wrong cause of contentDocument failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/skills/interaction-skills/iframes.md, line 69:

<comment>`sandbox="allow-same-origin"` is described as a blocker, but it actually enables same-origin DOM access. This guidance can mislead users to debug the wrong cause of `contentDocument` failures.</comment>

<file context>
@@ -0,0 +1,69 @@
+
+- A frame that was same-origin can become cross-origin after navigation inside it (e.g. OAuth redirect). Re-check with `contentDocument` truthiness.
+- `iframe.contentDocument === null` right after insertion — wait for `load` on the iframe before reading.
+- CSP `frame-ancestors`/`sandbox="allow-same-origin"` can block `contentDocument` access even when origins match.
</file context>
Suggested change
- CSP `frame-ancestors`/`sandbox="allow-same-origin"` can block `contentDocument` access even when origins match.
- CSP `frame-ancestors` can block the iframe from loading, and `sandbox` **without** `allow-same-origin` can block `contentDocument` access even when URLs look same-origin.
Fix with Cubic


1. `await session.connect()` — auto-detect the running browser.
2. `const tabs = await listPageTargets()` — see what real pages exist.
3. `await session.use(tabs[0].targetId)` — route Page/DOM/Runtime/Network calls to that target.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Handle the empty tabs case in the startup sequence before accessing tabs[0].targetId, otherwise this snippet can throw on fresh browser starts with no real page targets.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/skills/interaction-skills/connection.md, line 72:

<comment>Handle the empty `tabs` case in the startup sequence before accessing `tabs[0].targetId`, otherwise this snippet can throw on fresh browser starts with no real page targets.</comment>

<file context>
@@ -0,0 +1,98 @@
+
+1. `await session.connect()` — auto-detect the running browser.
+2. `const tabs = await listPageTargets()` — see what real pages exist.
+3. `await session.use(tabs[0].targetId)` — route Page/DOM/Runtime/Network calls to that target.
+4. `await session.Target.activateTarget({ targetId: tabs[0].targetId })` — bring the tab visually to front.
+5. Enable the domains you need: `await session.Page.enable()`, `await session.Network.enable({})`, etc.
</file context>
Fix with Cubic

const iframe = targetInfos.find(t => t.type === 'iframe' && t.url.includes('stripe.com'))

// Route subsequent calls to the iframe target
await session.use(iframe.targetId)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard the find(...) result before using iframe.targetId to avoid a runtime crash when no matching OOPIF target exists yet.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/skills/interaction-skills/cross-origin-iframes.md, line 26:

<comment>Guard the `find(...)` result before using `iframe.targetId` to avoid a runtime crash when no matching OOPIF target exists yet.</comment>

<file context>
@@ -0,0 +1,67 @@
+const iframe = targetInfos.find(t => t.type === 'iframe' && t.url.includes('stripe.com'))
+
+// Route subsequent calls to the iframe target
+await session.use(iframe.targetId)
+
+await session.Runtime.enable()
</file context>
Suggested change
await session.use(iframe.targetId)
if (!iframe) throw new Error('iframe target not found; interact first and re-query Target.getTargets')
await session.use(iframe.targetId)
Fix with Cubic


## CDP path: `pierceShadow`

`DOM.querySelector` / `DOM.querySelectorAll` accept `pierceShadow: true` — one call crosses every open shadow boundary:
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The Shadow DOM guidance documents a non-existent CDP option (pierceShadow on DOM.querySelector/DOM.querySelectorAll). Use DOM.getDocument(..., pierce: true) / shadow-root traversal before querying.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/skills/interaction-skills/shadow-dom.md, line 11:

<comment>The Shadow DOM guidance documents a non-existent CDP option (`pierceShadow` on `DOM.querySelector`/`DOM.querySelectorAll`). Use `DOM.getDocument(..., pierce: true)` / shadow-root traversal before querying.</comment>

<file context>
@@ -0,0 +1,80 @@
+
+## CDP path: `pierceShadow`
+
+`DOM.querySelector` / `DOM.querySelectorAll` accept `pierceShadow: true` — one call crosses every open shadow boundary:
+
+```js
</file context>
Suggested change
`DOM.querySelector` / `DOM.querySelectorAll` accept `pierceShadow: true` — one call crosses every open shadow boundary:
`DOM.querySelector` / `DOM.querySelectorAll` do **not** support a `pierceShadow` option; use `DOM.getDocument({ depth: -1, pierce: true })` (or `DOM.describeNode(..., pierce: true)`) to traverse shadow roots first, then query within discovered shadow-root node IDs.
Fix with Cubic

@@ -0,0 +1,12 @@
Provision a Browser Use cloud browser and bind it to this session.

After this tool returns, every subsequent `browser_execute` snippet drives the cloud browser via the same `session` object — there is no per-snippet attach step. The cloud browser is automatically stopped when the bcode session ends.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The tool description claims cloud browsers are stopped at bcode session end, but current implementation notes they usually persist until process exit because session eviction is not currently called.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/tool/browser-open-cloud.txt, line 3:

<comment>The tool description claims cloud browsers are stopped at bcode session end, but current implementation notes they usually persist until process exit because session eviction is not currently called.</comment>

<file context>
@@ -0,0 +1,12 @@
+Provision a Browser Use cloud browser and bind it to this session.
+
+After this tool returns, every subsequent `browser_execute` snippet drives the cloud browser via the same `session` object — there is no per-snippet attach step. The cloud browser is automatically stopped when the bcode session ends.
+
+Use this when:
</file context>
Suggested change
After this tool returns, every subsequent `browser_execute` snippet drives the cloud browser via the same `session` object — there is no per-snippet attach step. The cloud browser is automatically stopped when the bcode session ends.
After this tool returns, every subsequent `browser_execute` snippet drives the cloud browser via the same `session` object — there is no per-snippet attach step. The cloud browser is stopped when the session is evicted (currently this is typically at process exit).
Fix with Cubic

// Option A: dismiss after navigating (CDP, safe, undetectable)
await session.Page.navigate({ url: 'https://new-url.com' })
try {
await session.Page.handleJavaScriptDialog({ accept: true }) // "Leave"
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The beforeunload “Option A” snippet has a race: it tries to handle the dialog immediately after navigation without waiting for the dialog-open event, so it can miss late-opening dialogs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/skills/interaction-skills/dialogs.md, line 69:

<comment>The `beforeunload` “Option A” snippet has a race: it tries to handle the dialog immediately after navigation without waiting for the dialog-open event, so it can miss late-opening dialogs.</comment>

<file context>
@@ -0,0 +1,75 @@
+// Option A: dismiss after navigating (CDP, safe, undetectable)
+await session.Page.navigate({ url: 'https://new-url.com' })
+try {
+  await session.Page.handleJavaScriptDialog({ accept: true })  // "Leave"
+} catch { /* no dialog — normal */ }
+
</file context>
Fix with Cubic


## Reusing code: write to the workspace, import from snippet

The agent-workspace is per-project: `./.bcode/agent-workspace/`. It's a flat directory of `.ts` files you own and edit with the standard `write`/`edit` tools. Saved scripts travel with the project (`.bcode/agent-workspace/` is committed by default), so `git clone && cd && bcode` shares them.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The docs contradict themselves about workspace layout (flat directory vs any depth/subdir). Clarify this to avoid users organizing scripts incorrectly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/bcode-browser/skills/BROWSER.md, line 72:

<comment>The docs contradict themselves about workspace layout (`flat directory` vs `any depth/subdir`). Clarify this to avoid users organizing scripts incorrectly.</comment>

<file context>
@@ -0,0 +1,113 @@
+
+## Reusing code: write to the workspace, import from snippet
+
+The agent-workspace is per-project: `./.bcode/agent-workspace/`. It's a flat directory of `.ts` files you own and edit with the standard `write`/`edit` tools. Saved scripts travel with the project (`.bcode/agent-workspace/` is committed by default), so `git clone && cd && bcode` shares them.
+
+Write once, import many:
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant