From 6d6ff61ce27bc3ed5b0898ebeb77df61e614af70 Mon Sep 17 00:00:00 2001 From: Ankit Ranjan Date: Tue, 18 Aug 2026 16:27:39 +0530 Subject: [PATCH 1/3] docs(skills): describe the browser run sandbox as QuickJS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reference called `page`, `context`, `browser`, and `console` "normal Playwright globals" and never used the word QuickJS. That reads as a Node-hosted Playwright script, which is the belief that produces `'Buffer' is not defined` and `'document' is not defined`. Rewrites the reference around the actual model — your code runs in QuickJS, not in the page and not in Node — and replaces "use the vendored Playwright client as the API reference" with tables of what is available and what is blocked, each naming the supported alternative. Also corrects two stale claims, both verified against the transport: - `context.newPage()` is supported; playwright-transport.ts routes `newPage` to `scope.createPage`. The doc said it was unavailable. - `page.$`/`page.$$` are supported; DENIED_METHODS blocks only Browser.*, BrowserContext.close/newCDPSession, Page.close, and Playwright.newRequest. The SKILL.md pointer now states the surprise instead of listing chapters, because a table of contents is skippable and a warning is not. Co-Authored-By: Claude Opus 5 --- skills/webcmd-browser/SKILL.md | 2 +- .../references/browser-run-playwright.md | 70 +++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/skills/webcmd-browser/SKILL.md b/skills/webcmd-browser/SKILL.md index 1049dd24..0041376c 100644 --- a/skills/webcmd-browser/SKILL.md +++ b/skills/webcmd-browser/SKILL.md @@ -94,7 +94,7 @@ return { title: await page.title(), url: page.url() }; JS ``` -For sandbox boundaries, artifacts, errors, snapshots, and timings, read [`references/browser-run-playwright.md`](references/browser-run-playwright.md). +**`browser run` executes in QuickJS — not in Node, and not in the page.** `document` and `window` are not in scope (use `page.evaluate`), and `Buffer`, `require`, and `fs` do not exist. Read [`references/browser-run-playwright.md`](references/browser-run-playwright.md) before writing your first program; it lists what is available, what is blocked, and what to use instead. --- diff --git a/skills/webcmd-browser/references/browser-run-playwright.md b/skills/webcmd-browser/references/browser-run-playwright.md index fdb2ac31..79321e16 100644 --- a/skills/webcmd-browser/references/browser-run-playwright.md +++ b/skills/webcmd-browser/references/browser-run-playwright.md @@ -1,18 +1,70 @@ # Browser Run Details -## Sandbox boundaries +## The runtime is QuickJS, not Node and not the page -`run` evaluates the supplied JavaScript in a fresh sandbox. Browser state in the bound session persists, but JavaScript variables and handles do not. `page`, `context`, `browser`, and `console` are normal Playwright globals; use the vendored Playwright client as the API reference. Return only JSON-compatible data. `page.snapshotForAI()` is not available. +`browser run` executes your program in a QuickJS sandbox. `page`, `context`, and `browser` +are Playwright handles that drive a browser running somewhere else — they are not evidence +that you are inside that browser, and not evidence that you are inside Node. -`context.newPage()` is not available inside `run`; create or bind Session tabs through Webcmd commands so page ownership stays deterministic. +Two consequences produce almost every `browser run` failure: -## Artifact paths +- **Anything DOM-shaped must go inside `page.evaluate()`.** `document`, `window`, and + `localStorage` are not in your scope. `document.querySelector(...)` at the top level + throws `'document' is not defined`; `await page.evaluate(() => document.querySelector(...))` + works, because that callback is serialized and run in the page. +- **Anything Node-shaped does not exist.** No `require`, no `import` of host modules, no + `fs`, no `Buffer`, no `process`. -Artifacts written by Playwright must use a relative logical filename. Webcmd returns an artifact receipt with its locator; it does not grant host-path write access. +Browser state in the bound session persists between runs. JavaScript variables and handles +do not — each run starts with a fresh scope. + +## What is available + +| Need | Use | +|---|---| +| Drive the page | `page`, `context`, `browser` (Playwright) | +| Read or manipulate the DOM | `page.evaluate(() => …)` | +| Find elements | `page.locator(selector)`, `page.getByRole(...)`, and the other `getBy*` locators | +| Log | `console` | +| Return data | `return` any JSON-compatible value | + +`page.$` and `page.$$` work, but prefer `page.locator()` — it retries and auto-waits. + +`context.newPage()` works and creates a tab the Webcmd session tracks. You cannot close it +from inside `run` (see below); list tabs with `webcmd --session browser tabs`. + +`page.snapshotForAI()` is not available; use `webcmd browser snapshot` instead. + +## What is blocked, and what to use instead + +These throw `BROWSER_RUN_API_UNSUPPORTED` because page and context ownership belongs to the +Webcmd session, not to your program: + +| Blocked | Instead | +|---|---| +| `page.close()` | Leave the tab open, or `webcmd session close ` | +| `context.close()`, `browser.close()` | `webcmd session close ` | +| `browser.newContext()` | `webcmd session create` — one run is scoped to one context | +| `browser.newBrowserCDPSession()`, `context.newCDPSession()` | Not exposed inside `run` | +| `playwright.request` (`newRequest`) | `page.request` for calls in the page's context | + +## Files and binary data + +There is no host filesystem. Passing a host path to `setInputFiles` fails with +`File paths are unavailable in the QuickJS sandbox; use in-memory file payloads` — supply +`{ name, mimeType, buffer }` with a `Uint8Array` instead. + +Artifacts written from the sandbox must use a relative logical filename. Webcmd returns an +artifact receipt with its locator; it does not grant host-path write access. ## Errors -`BROWSER_RUN_*` errors name invalid input, unsupported Playwright calls, timeouts, output limits, or serialization failures. A timeout can include `BROWSER_RUN_SIDE_EFFECTS_MAY_HAVE_OCCURRED`; inspect the page state before retrying a write. +`BROWSER_RUN_*` errors name invalid input, unsupported Playwright calls, timeouts, output +limits, or serialization failures. A timeout can include +`BROWSER_RUN_SIDE_EFFECTS_MAY_HAVE_OCCURRED`; inspect the page state before retrying a write. + +A rejection phrased `QuickJS promise rejected: 'X' is not defined` means `X` is a Node or DOM +global that the sandbox does not provide — check the two tables above before retrying. ## Snapshot behavior @@ -21,3 +73,9 @@ Use `webcmd --session browser snapshot --snapshot-mode act` to insp ## Timing Run results include timing fields such as `quickjs_boot_ms`, `client_bundle_init_ms`, `program_ms`, `browser_wait_ms`, and `snapshot_ms`. `--timeout ` limits the complete run; `--max-output ` bounds returned data and logs. + +## Hosted mode + +Hosted `browser run` uses the same QuickJS sandbox and the same rules. Only the browser on +the far end differs — hosted runs drive a Browser Use browser over CDP rather than local +Cloak. Programs that work locally work hosted; the tables above apply in both modes. From bf69441b6d5a45d4d1ea2ec642e0b9301ce908b1 Mon Sep 17 00:00:00 2001 From: Ankit Ranjan Date: Tue, 18 Aug 2026 23:12:54 +0530 Subject: [PATCH 2/3] test(skills): assert the rewritten QuickJS reference structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skills content guard pinned the old "Sandbox boundaries" and "Artifact paths" headings, which this branch renamed. Assert the concepts the doc now teaches instead — the QuickJS runtime, the page.evaluate boundary, and artifacts — which is a stronger check than the old heading match. Co-Authored-By: Claude Opus 5 --- src/skills.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/skills.test.ts b/src/skills.test.ts index ea19e4a8..9667ab6e 100644 --- a/src/skills.test.ts +++ b/src/skills.test.ts @@ -215,8 +215,9 @@ describe('webcmd skills content', () => { expect(browser).toContain('--no-snapshot-diff'); expect(browser).not.toContain('page.snapshotForAI()'); expect(browser).not.toContain('--snapshot-diff'); - expect(browserRunReference).toMatch(/sandbox boundaries/i); - expect(browserRunReference).toMatch(/artifact paths/i); + expect(browserRunReference).toMatch(/QuickJS/); + expect(browserRunReference).toMatch(/page\.evaluate/); + expect(browserRunReference).toMatch(/artifact/i); expect(browserRunReference).toMatch(/errors/i); expect(browserRunReference).toMatch(/snapshot behavior/i); expect(browserRunReference).toContain('--snapshot-mode act|tree'); From 2c7c047cd77df4a4c50a705a72e1d91908df2717 Mon Sep 17 00:00:00 2001 From: Ankit Ranjan Date: Tue, 18 Aug 2026 23:28:27 +0530 Subject: [PATCH 3/3] docs(skills): move the QuickJS rewrite into the litprompt source tree #347 moved published skills to skill-src/*.src.md sources built by litprompt. The rewrite and SKILL.md pointer must live in the sources so 'make verify' passes; the published .md files are regenerated from them. Co-Authored-By: Claude Opus 5 --- skill-src/webcmd-browser/SKILL.src.md | 2 +- .../references/browser-run-playwright.src.md | 70 +++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/skill-src/webcmd-browser/SKILL.src.md b/skill-src/webcmd-browser/SKILL.src.md index 2cb1c4ec..f52e2868 100644 --- a/skill-src/webcmd-browser/SKILL.src.md +++ b/skill-src/webcmd-browser/SKILL.src.md @@ -94,7 +94,7 @@ return { title: await page.title(), url: page.url() }; JS ``` -For sandbox boundaries, artifacts, errors, snapshots, and timings, read [`references/browser-run-playwright.md`](references/browser-run-playwright.md). +**`browser run` executes in QuickJS — not in Node, and not in the page.** `document` and `window` are not in scope (use `page.evaluate`), and `Buffer`, `require`, and `fs` do not exist. Read [`references/browser-run-playwright.md`](references/browser-run-playwright.md) before writing your first program; it lists what is available, what is blocked, and what to use instead. --- diff --git a/skill-src/webcmd-browser/references/browser-run-playwright.src.md b/skill-src/webcmd-browser/references/browser-run-playwright.src.md index fdb2ac31..79321e16 100644 --- a/skill-src/webcmd-browser/references/browser-run-playwright.src.md +++ b/skill-src/webcmd-browser/references/browser-run-playwright.src.md @@ -1,18 +1,70 @@ # Browser Run Details -## Sandbox boundaries +## The runtime is QuickJS, not Node and not the page -`run` evaluates the supplied JavaScript in a fresh sandbox. Browser state in the bound session persists, but JavaScript variables and handles do not. `page`, `context`, `browser`, and `console` are normal Playwright globals; use the vendored Playwright client as the API reference. Return only JSON-compatible data. `page.snapshotForAI()` is not available. +`browser run` executes your program in a QuickJS sandbox. `page`, `context`, and `browser` +are Playwright handles that drive a browser running somewhere else — they are not evidence +that you are inside that browser, and not evidence that you are inside Node. -`context.newPage()` is not available inside `run`; create or bind Session tabs through Webcmd commands so page ownership stays deterministic. +Two consequences produce almost every `browser run` failure: -## Artifact paths +- **Anything DOM-shaped must go inside `page.evaluate()`.** `document`, `window`, and + `localStorage` are not in your scope. `document.querySelector(...)` at the top level + throws `'document' is not defined`; `await page.evaluate(() => document.querySelector(...))` + works, because that callback is serialized and run in the page. +- **Anything Node-shaped does not exist.** No `require`, no `import` of host modules, no + `fs`, no `Buffer`, no `process`. -Artifacts written by Playwright must use a relative logical filename. Webcmd returns an artifact receipt with its locator; it does not grant host-path write access. +Browser state in the bound session persists between runs. JavaScript variables and handles +do not — each run starts with a fresh scope. + +## What is available + +| Need | Use | +|---|---| +| Drive the page | `page`, `context`, `browser` (Playwright) | +| Read or manipulate the DOM | `page.evaluate(() => …)` | +| Find elements | `page.locator(selector)`, `page.getByRole(...)`, and the other `getBy*` locators | +| Log | `console` | +| Return data | `return` any JSON-compatible value | + +`page.$` and `page.$$` work, but prefer `page.locator()` — it retries and auto-waits. + +`context.newPage()` works and creates a tab the Webcmd session tracks. You cannot close it +from inside `run` (see below); list tabs with `webcmd --session browser tabs`. + +`page.snapshotForAI()` is not available; use `webcmd browser snapshot` instead. + +## What is blocked, and what to use instead + +These throw `BROWSER_RUN_API_UNSUPPORTED` because page and context ownership belongs to the +Webcmd session, not to your program: + +| Blocked | Instead | +|---|---| +| `page.close()` | Leave the tab open, or `webcmd session close ` | +| `context.close()`, `browser.close()` | `webcmd session close ` | +| `browser.newContext()` | `webcmd session create` — one run is scoped to one context | +| `browser.newBrowserCDPSession()`, `context.newCDPSession()` | Not exposed inside `run` | +| `playwright.request` (`newRequest`) | `page.request` for calls in the page's context | + +## Files and binary data + +There is no host filesystem. Passing a host path to `setInputFiles` fails with +`File paths are unavailable in the QuickJS sandbox; use in-memory file payloads` — supply +`{ name, mimeType, buffer }` with a `Uint8Array` instead. + +Artifacts written from the sandbox must use a relative logical filename. Webcmd returns an +artifact receipt with its locator; it does not grant host-path write access. ## Errors -`BROWSER_RUN_*` errors name invalid input, unsupported Playwright calls, timeouts, output limits, or serialization failures. A timeout can include `BROWSER_RUN_SIDE_EFFECTS_MAY_HAVE_OCCURRED`; inspect the page state before retrying a write. +`BROWSER_RUN_*` errors name invalid input, unsupported Playwright calls, timeouts, output +limits, or serialization failures. A timeout can include +`BROWSER_RUN_SIDE_EFFECTS_MAY_HAVE_OCCURRED`; inspect the page state before retrying a write. + +A rejection phrased `QuickJS promise rejected: 'X' is not defined` means `X` is a Node or DOM +global that the sandbox does not provide — check the two tables above before retrying. ## Snapshot behavior @@ -21,3 +73,9 @@ Use `webcmd --session browser snapshot --snapshot-mode act` to insp ## Timing Run results include timing fields such as `quickjs_boot_ms`, `client_bundle_init_ms`, `program_ms`, `browser_wait_ms`, and `snapshot_ms`. `--timeout ` limits the complete run; `--max-output ` bounds returned data and logs. + +## Hosted mode + +Hosted `browser run` uses the same QuickJS sandbox and the same rules. Only the browser on +the far end differs — hosted runs drive a Browser Use browser over CDP rather than local +Cloak. Programs that work locally work hosted; the tables above apply in both modes.