4.4 KiB
RunUserScript
Executes a user-authored Playwright browser-macro from the caller's
browser-macros/ folder.
Retired (2026-06): plain-Node
scripts/(the oldkind: 'script') andtemplates/were removed. Keep reusable procedures/boilerplate in Skills, and run ad-hoc code (Node, Python, …) with the Bash tool. Passingkind: 'script'now returns an error pointing at those replacements.
| directory | runtime | signature | use case |
|---|---|---|---|
browser-macros/ |
Playwright — Chromium | main({ context, params }) |
Web automation with a live browser session |
Input
{
name: string, // filename — '.js' is appended if absent
params?: Record<string, unknown>, // runtime values matching the macro's param spec
}
Param validation
Params are validated against the params: block in the macro's YAML frontmatter:
- Extra params not listed in the spec → error containing "param"
- Wrong type for a declared param → error containing "param"
- Missing required param (no default) → error containing "param"
- Params with defaults are filled in automatically when not supplied
On any param error the tool returns isError: true immediately — no subprocess is spawned.
Session integration
If the macro's frontmatter declares session_profile_id: <N>, the tool:
- Loads the profile from the DB (owner-gated — must belong to
ctx.userId). - Decrypts the user's envelope-encrypted DEK using the master key.
- Decrypts the AES-GCM storageState blob using the DEK.
- Passes the decrypted Playwright
storageStateobject to the child process.
If any step fails the tool returns isError: true with a descriptive message.
Self-healing recorder
When a macro fails at runtime, the tool automatically enables the BrowseWeb
recorder for the current task (if not already enabled). On task completion,
recording-flush stages a candidate patch as browser-macros/{name}.next.js
for diff review.
Output format
On success:
<result stringified>
[script logs]
<console.log lines from the child process>
The result is JSON-stringified if it is an object or array; String(result) otherwise. The [script logs] section is only appended when the macro produced logs.
On failure:
RunUserScript "{name}" failed: <error message>
The recorder is now enabled for this task; subsequent BrowseWeb actions will be captured.
On task complete, a candidate patch will be saved as browser-macros/{name}.next.js for review.
Error cases
| Situation | isError |
message contains |
|---|---|---|
| No authenticated user | true | "authenticated" |
| Macro file not found | true | "not found" |
Retired kind: 'script' passed |
true | "retired" |
| Frontmatter parse error | true | "frontmatter" |
| Param type / missing error | true | "param" |
| Session profile not found / not owned | true | "not found or does not belong" |
| Profile not active | true | "not active" |
| DEK / blob decryption failure | true | "decrypt" |
| Macro timeout (60 s) | true | "timeout" |
| Macro exits non-zero | true | "exited code" |
Notes
- The tool is a META_TOOL — it is available in every movement without listing it in
allowed_tools. - Use
ListUserAssetsfirst to discover available macros and their param specs. - On macro failure, use
BrowseWebas a manual fallback. - To run Python or other ad-hoc code, use the Bash tool (pip packages pre-baked).
Security and trust model
RunUserScript is disabled by default. To enable it, add to config.yaml:
tools:
user_scripts_enabled: true
Only enable for trusted users. Macros run in a restricted child process:
- Env is scrubbed — only
PATH,HOME,TMPDIR/TMP,LANG,NODE_ENV, andPLAYWRIGHT_BROWSERS_PATHare forwarded. API keys, database passwords, and other secrets in the orchestrator's environment are not visible to the macro. - CWD is set to the system tmpdir, not the orchestrator workspace.
- Stdout is capped at 1 MB and stderr at 200 KB; exceeding either limit kills the child.
- On timeout, the entire process group (including Playwright's Chromium) is killed.
Browser-macros cannot use Node's --permission model — Chromium launch, native
bindings, and outbound HTTPS all need unrestricted child_process/addons/network.
They run with full Node.js capability (env-scrubbed only) and rely on
container-level isolation. Treat them as trusted code.