This commit is contained in:
+34
-52
@@ -1,27 +1,29 @@
|
||||
# RunUserScript
|
||||
|
||||
Executes a user-authored script from the caller's user folder.
|
||||
Executes a user-authored Playwright browser-macro from the caller's
|
||||
`browser-macros/` folder.
|
||||
|
||||
Two kinds of scripts are supported:
|
||||
> **Retired (2026-06):** plain-Node `scripts/` (the old `kind: 'script'`) and
|
||||
> `templates/` were removed. Keep reusable procedures/boilerplate in **Skills**,
|
||||
> and run ad-hoc code (Node, Python, …) with the **Bash** tool. Passing
|
||||
> `kind: 'script'` now returns an error pointing at those replacements.
|
||||
|
||||
| kind | directory | runtime | signature | use case |
|
||||
|------|-----------|---------|-----------|----------|
|
||||
| `'script'` (default) | `scripts/` | plain Node.js — no Chromium | `main({ params })` | Data processing, API calls, computation, file conversion |
|
||||
| `'browser-macro'` | `browser-macros/` | Playwright — Chromium | `main({ context, params })` | Web automation with a live browser session |
|
||||
| directory | runtime | signature | use case |
|
||||
|-----------|---------|-----------|----------|
|
||||
| `browser-macros/` | Playwright — Chromium | `main({ context, params })` | Web automation with a live browser session |
|
||||
|
||||
## Input
|
||||
|
||||
```ts
|
||||
{
|
||||
name: string, // filename — '.js' is appended if absent
|
||||
params?: Record<string, unknown>, // runtime values matching the script's param spec
|
||||
kind?: 'script' | 'browser-macro' // default: 'script'
|
||||
params?: Record<string, unknown>, // runtime values matching the macro's param spec
|
||||
}
|
||||
```
|
||||
|
||||
## Param validation
|
||||
|
||||
Params are validated against the `params:` block in the script's YAML frontmatter:
|
||||
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"
|
||||
@@ -29,9 +31,9 @@ Params are validated against the `params:` block in the script's YAML frontmatte
|
||||
|
||||
On any param error the tool returns `isError: true` immediately — no subprocess is spawned.
|
||||
|
||||
## Session integration (browser-macro only)
|
||||
## Session integration
|
||||
|
||||
If a `browser-macro` script's frontmatter declares `session_profile_id: <N>`, the tool:
|
||||
If the macro's frontmatter declares `session_profile_id: <N>`, the tool:
|
||||
|
||||
1. Loads the profile from the DB (owner-gated — must belong to `ctx.userId`).
|
||||
2. Decrypts the user's envelope-encrypted DEK using the master key.
|
||||
@@ -40,13 +42,12 @@ If a `browser-macro` script's frontmatter declares `session_profile_id: <N>`, th
|
||||
|
||||
If any step fails the tool returns `isError: true` with a descriptive message.
|
||||
|
||||
For `kind: 'script'` (plain runtime), `session_profile_id` in the frontmatter is ignored — no session is loaded.
|
||||
## Self-healing recorder
|
||||
|
||||
## Self-healing recorder (browser-macro only)
|
||||
|
||||
When a `browser-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.
|
||||
|
||||
Plain scripts (`kind: 'script'`) do **not** auto-enable the 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
|
||||
|
||||
@@ -58,14 +59,9 @@ On success:
|
||||
<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 script produced logs.
|
||||
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 (plain):
|
||||
```
|
||||
RunUserScript "{name}" failed: <error message>
|
||||
```
|
||||
|
||||
On failure (browser-macro):
|
||||
On failure:
|
||||
```
|
||||
RunUserScript "{name}" failed: <error message>
|
||||
|
||||
@@ -78,36 +74,22 @@ On task complete, a candidate patch will be saved as browser-macros/{name}.next.
|
||||
| Situation | `isError` | message contains |
|
||||
|-----------|-----------|-----------------|
|
||||
| No authenticated user | true | "authenticated" |
|
||||
| Script file not found | true | "not found" |
|
||||
| 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" |
|
||||
| Script timeout (60 s) | true | "timeout" |
|
||||
| Script exits non-zero | true | "exited code" |
|
||||
| Plain script denied child_process (e.g. spawning python) | true | "exited code" + "use the Bash tool" |
|
||||
| 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 `kind: 'browser-macro'` for any script that needs a browser (`context`).
|
||||
- Use `ListUserAssets` first to discover available scripts and their param specs.
|
||||
- On browser-macro failure, use `BrowseWeb` as a manual fallback.
|
||||
|
||||
## Running Python (don't — use Bash)
|
||||
|
||||
`RunUserScript` runs **Node only**. There is no Python interpreter path. A
|
||||
common footgun is to write a Node script that does
|
||||
`child_process.spawn('python3', ...)` and run it here — that **cannot work**:
|
||||
plain scripts run under Node's `--permission` model, which denies
|
||||
`child_process` entirely (you get `ERR_ACCESS_DENIED`). Even if it were
|
||||
allowed, the child's env is scrubbed, so it would not see the orchestrator's
|
||||
provisioned Python environment.
|
||||
|
||||
To run Python, use the **`Bash` tool** instead: `python3 your_script.py`. The
|
||||
Bash sandbox has the pip packages pre-baked (pypdf, pdfplumber, python-docx,
|
||||
python-pptx, openpyxl, pandas, numpy, …). That is the supported, working path.
|
||||
- Use `ListUserAssets` first to discover available macros and their param specs.
|
||||
- On macro failure, use `BrowseWeb` as a manual fallback.
|
||||
- To run Python or other ad-hoc code, use the **Bash** tool (pip packages pre-baked).
|
||||
|
||||
## Security and trust model
|
||||
|
||||
@@ -118,13 +100,13 @@ tools:
|
||||
user_scripts_enabled: true
|
||||
```
|
||||
|
||||
**Only enable for trusted users.** User scripts run in a restricted child process:
|
||||
- Env is scrubbed — only `PATH`, `HOME`, `TMPDIR/TMP`, `LANG`, `NODE_ENV`, and `PLAYWRIGHT_BROWSERS_PATH` are forwarded. API keys, database passwords, and other secrets in the orchestrator's environment are not visible to the script.
|
||||
**Only enable for trusted users.** Macros run in a restricted child process:
|
||||
- Env is scrubbed — only `PATH`, `HOME`, `TMPDIR/TMP`, `LANG`, `NODE_ENV`, and `PLAYWRIGHT_BROWSERS_PATH` are 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 for browser-macros) is killed.
|
||||
- On timeout, the entire process group (including Playwright's Chromium) is killed.
|
||||
|
||||
**The two runtimes have different capability levels:**
|
||||
|
||||
- **Plain scripts (`kind: 'script'`)** run under Node's Permissions Model (`--permission`): `--allow-fs-read` is limited to the child-runner dir and tmpdir, `--allow-fs-write` to tmpdir, and `child_process`, worker threads, and native addons are **denied**. A plain script that tries to spawn a subprocess (e.g. python) fails with `ERR_ACCESS_DENIED`. See "Running Python" above.
|
||||
- **Browser-macros (`kind: 'browser-macro'`)** cannot use `--permission` — 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.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user