Record any web app operation once, AI turns it into a reusable automation tool. Use when: (1) automating repetitive tasks on any web application (reports, su...
Record once in any web app, let AI handle it from now on.
Record → Analyze → Confirm Fields → Generate → Test → Register as Tool
🎬 Record User performs the workflow once in a real browser (after login)
🔍 Analyze AI analyzes network traffic, classifies fixed/dynamic/session fields
✅ Confirm Fields [Required for submit tasks] User confirms field classifications
📝 Generate Generates reusable TS script + field mapping
🧪 Test Iterative test loop, up to 5 rounds of auto-fix
🔧 Register Register as an OpenClaw tool for direct invocation
Data extraction and report generation. Scripts run and output results automatically — no manual intervention needed. Examples: pull sales reports, extract project data, export revenue details
Submit forms such as expense reports, travel requests, payment requests, etc. Each run requires dynamic parameters. Examples: submit travel request, submit expense report, submit payment request
The key challenge for submit tasks: correctly distinguishing which fields are fixed vs. which change every time, and confirming with the user before generating the script.
~/.openclaw/rpa/
├── recordings/<task-name>/recording.json
├── tasks/<task-name>/
│ ├── task-meta.json
│ ├── run.ts
│ └── field-mapping.json
└── sessions/<domain>.session.json
Skill scripts: /opt/homebrew/lib/node_modules/openclaw/skills/web-autopilot/scripts/
record — Record a workflowAsk user: task name, login URL or app URL.
cd /opt/homebrew/lib/node_modules/openclaw/skills/web-autopilot
# Option A: Start from login page (SSO, OAuth, username/password, etc.)
npx ts-node scripts/record.ts --name "my-task" --sso-url "https://login.example.com"
# Option B: Start directly from app (if already logged in or no login needed)
npx ts-node scripts/record.ts --name "my-task" --app-url "https://app.example.com"
Run in PTY mode (pty: true, background: true). User operates browser, types "done" when finished.
Note: --sso-url is a legacy parameter name; it works for any login URL (SSO, OAuth, plain login page, etc.).
analyze — Analyze the recording (AI does this)Read recording.json, separate login traffic from business traffic, identify core APIs.
Key steps:
~/.openclaw/rpa/recordings/<task>/summary.txt for overview| Type | Meaning | Handling |
|---|---|---|
| FIXED | Same value every submission (approval flow ID, company entity, currency, expense type enums…) | Hardcoded in script |
| DYNAMIC | Different each submission (amount, date, reason, attachment path…) | Becomes CLI --parameter |
| SESSION | Auth tokens/cookies, auto-managed | Injected by session.ts |
| RELATIONAL | Requires a lookup from another API to get the ID (e.g., project ID, person ID…) | Auto-queried in script, or exposed as DYNAMIC parameter |
Every field must have a human-readable label. Including system-generated field names.
Inference priority:
(unknown meaning: sample value)After analysis, you must present the following confirmation table to the user and wait for confirmation before generating the script:
📋 Field Classification Confirmation — <task name>
✅ FIXED (hardcoded):
- approvalFlowId: "xxx" → Approval Flow ID
- companyId: "yyy" → Company Entity
- currency: "CNY" → Currency
🔄 DYNAMIC (passed as parameters each run):
- amount → Amount (example: --amount 1500)
- startDate → Start Date (example: --startDate 2026-03-10)
- endDate → End Date (example: --endDate 2026-03-12)
- destination → Destination (example: --destination "New York")
- reason → Reason (example: --reason "Client visit")
- attachments → Attachment path (example: --attachments ~/Desktop/receipt.jpg)
🔗 RELATIONAL (auto-queried):
- projectId → Project ID (auto-looked up by project name, --projectName "Project X")
❓ Needs confirmation (AI uncertain):
- field_abc123 → Unknown meaning (recorded value: "0"), suggest: FIXED("0") or DYNAMIC?
Please confirm the above classification or indicate any fields that need adjustment.
Only proceed to the Generate step after user confirmation.
csv.writer + proper quoting to handle JSON fields containing commasgenerate — Generate the task scriptPre-generation checklist (Query/Export tasks):
Pre-generation checklist (Submit tasks):
--dry-run mode (prints request body without submitting, for testing)Submit task invocation example (written to task-meta.json usage field after generation):
# Preview (no actual submission)
npx ts-node run.ts --dry-run --amount 1500 --startDate 2026-03-10 ...
# Submit for real
npx ts-node run.ts --amount 1500 --startDate 2026-03-10 --destination "New York" --reason "Client visit"
test — Iterative test loop (max 5 rounds)Run script → check output → if error: diagnose → fix → repeat.
| Error | Cause | Fix |
|---|---|---|
| 401/403 | Session expired / wrong auth | Re-check auth headers, re-login |
| 400 | Wrong field name/type | Compare with recording |
| 404 | Wrong URL | Check URL exactly |
| JSON parse error | Response is HTML | Log resp.raw |
run — Execute a registered tasknpx ts-node ~/.openclaw/rpa/tasks/<task>/run.ts --param1 value1
list — List all tasksnpx ts-node /opt/homebrew/lib/node_modules/openclaw/skills/web-autopilot/scripts/run-task.ts --list
Sessions are cookie-based and work with any login method:
Session files: ~/.openclaw/rpa/sessions/<domain>.session.json
Login credentials are stored encrypted (AES-256-GCM) in a separate file — never stored in plaintext.
File: ~/.openclaw/rpa/credentials.enc
RPA_CREDENTIAL_KEY env var# Manage credentials
npx ts-node scripts/utils/credentials.ts list # List saved domains + usernames
npx ts-node scripts/utils/credentials.ts save <domain> <user> <pass> # Save manually
npx ts-node scripts/utils/credentials.ts delete <domain> # Delete
npx ts-node scripts/utils/credentials.ts extract <recording.json> # Extract from recording
When a session expires, the auto-login flow kicks in:
1. Read encrypted credentials for the target domain from credentials.enc
2. Select login strategy based on loginFlow.type
3. Launch browser (headless if credentials exist, headed if not)
4. Execute login steps → follow redirects → reach target app
5. Capture cookies/tokens → save new session
6. If all else fails → open headed browser for manual login (fallback)
When generating scripts, you must identify the login type from the recording and write it to the loginFlow field in task-meta.json:
| type | Scenario | Auto-login method | Example |
|---|---|---|---|
api | SSO/app provides a REST login endpoint, single POST completes auth | Call API directly → follow redirects | Enterprise SSO (POST /api/sso/login) |
form | Single-page login form (username + password on same page) | Fill form fields → click submit | Common admin dashboards |
multi-step | Multi-step login (email → next page → password → next page → possible 2FA) | Execute step sequence | Google, Microsoft, Okta |
manual-only | Has CAPTCHA/2FA/risk control, cannot be fully automated | Open headed browser directly | Banking systems, strong CAPTCHA sites |
{
"loginFlow": {
"type": "api", // api | form | multi-step | manual-only
"loginUrl": "https://sso.example.com",
"loginDomain": "sso.example.com",
"appDomain": "app.example.com",
// ── type=api specific fields ──
"loginApiPath": "/api/sso/login",
"authType": "passwordAuth", // Optional, auth type field in API body
"appId": "1234567890", // Optional, SSO portal app ID (for forward redirect)
"appForwardUrl": "...", // Optional, direct redirect URL (alternative to appId)
// ── type=form specific fields ──
"usernameSelector": "input[name='email']", // Optional, custom selectors
"passwordSelector": "input[type='password']",
"submitSelector": "button[type='submit']",
// ── type=multi-step specific fields ──
"steps": [
{ "action": "fill", "selector": "input[type=email]", "field": "username" },
{ "action": "click", "selector": "#identifierNext" },
{ "action": "wait", "selector": "input[type=password]", "timeoutMs": 5000 },
{ "action": "fill", "selector": "input[type=password]", "field": "password" },
{ "action": "click", "selector": "#passwordNext" }
],
// ── Common fields ──
"successIndicator": "url_contains:app.example.com", // Condition to detect successful login
"postLoginWaitMs": 3000 // Wait time after login success (for cookies to settle)
}
}
During the analyze step, you must complete the following login analysis:
credentials.ts extract <recording.json> (auto-detects username/password in POST body)POST login/auth API → type = apiformmulti-stepmanual-onlytask-meta.json[REDACTED]⚠️ If credentials.ts extract cannot extract credentials (e.g., Google multi-step login), prompt the user to save credentials manually:
npx ts-node scripts/utils/credentials.ts save accounts.google.com user@gmail.com 'password'
Choose the auto-login implementation based on loginFlow.type:
type=api (REST API login):
// API login → follow redirects → navigate to app
const resp = await page.evaluate(async (p) => {
const r = await fetch(p.url, { method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify(p.body), credentials: 'include' });
return { status: r.status, ok: r.ok };
}, { url: loginApiUrl, body: { authType, credential: { username, password } } });
type=form:
await page.fill(loginFlow.usernameSelector || 'input[name="username"]', cred.username);
await page.fill(loginFlow.passwordSelector || 'input[type="password"]', cred.password);
await page.click(loginFlow.submitSelector || 'button[type="submit"]');
type=multi-step:
for (const step of loginFlow.steps) {
if (step.action === 'fill') {
const value = step.field === 'username' ? cred.username : cred.password;
await page.fill(step.selector, value);
} else if (step.action === 'click') {
await page.click(step.selector);
} else if (step.action === 'wait') {
await page.waitForSelector(step.selector, { timeout: step.timeoutMs || 10000 });
}
}
type=manual-only:
// Open headed browser, wait for user to complete login manually
const browser = await pw.chromium.launch({ headless: false });
// ... wait for successIndicator
task-meta.json loginFlow example (SSO → enterprise app):
{
"loginFlow": {
"type": "api",
"loginUrl": "https://sso.example.com",
"loginDomain": "sso.example.com",
"loginApiPath": "/api/sso/login",
"authType": "passwordAuth",
"appId": "1234567890",
"appDomain": "app.example.com",
"successIndicator": "url_contains:app.example.com"
}
}
[REDACTED])type=api mode (enterprise SSO → business app)type=multi-step + steps sequencetype=manual-onlyrequire() is unavailabletsconfig.json in the task directory to force "module": "commonjs"require('/opt/.../node_modules/playwright')Some login flows or apps open new tabs. Recorder uses context.on('request/response') to capture ALL tabs.
field_*, attr_*, custom_*) must be analyzed from sample data--dry-run in generated scripts so users can verify the request body before committing| Item | Path |
|---|---|
| Recorder | scripts/record.ts |
| Task runner | scripts/run-task.ts |
| Session utility | scripts/utils/session.ts |
| Login helper | scripts/utils/login.ts |
| Recordings | ~/.openclaw/rpa/recordings/<task>/ |
| Generated tasks | ~/.openclaw/rpa/tasks/<task>/ |
| Sessions | ~/.openclaw/rpa/sessions/<domain>.session.json |
ZIP package — ready to use