@qqbrowser/qbot-dsh 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/agent-presets/code/agent.cordis.yml +3 -0
- package/config/agent-presets/minimal/agent.cordis.yml +6 -1
- package/config/agent-presets/standard/agent.cordis.yml +3 -0
- package/config/skills/bilibili-video-download/SKILL.md +84 -0
- package/config/skills/bilibili-video-download/bilibili.webmcp.tools.js +53 -0
- package/config/skills/qqbrowser-use/SKILL.md +444 -0
- package/config/skills/qqbrowser-use/references/commands-extended.md +74 -0
- package/config/skills/qqbrowser-use/references/playbook.md +671 -0
- package/config/skills/qqbrowser-use/references/session-lifecycle.md +102 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +1 -1
- package/lib/index.js +4 -1
- package/lib/sandbox-local.js +1 -0
- package/node_modules/@deepseek-ai/cordis/package.json +1 -1
- package/node_modules/@deepseek-ai/cordis-plugin-include/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-agent/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-agent-default-model/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-agent-instructions/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-agent-loop/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-agent-presets/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-anonymous-user-id/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-api-remotes/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-app-boot/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-attachment/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-attachment-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-base/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-bash-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-bash-sandbox/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-connection/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-hmr/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-locale/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-locale/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-runtime/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-agent-preset/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-agent-preset/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-attachment/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-commands/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-commands/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js +5 -5
- package/node_modules/@deepseek-ai/dsh-client-ui-conversation/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-cordis/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-cordis/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-deliverables/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-directory-picker-browse/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-directory-picker-browse/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-directory-picker-native/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-goal/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-goal/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-input-trigger/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-input-trigger/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-jobs/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-jobs/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-layout/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-layout/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-message-feedback/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-message-feedback/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-model-selection/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-permission-presets/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-permission-presets/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-plan/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-plan/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings-general/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings-models/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings-models/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings-plugin-inventory/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings-plugin-inventory/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings-plugins/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-settings-plugins/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-sidebar/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-skill/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-skill/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-subagent/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-subagent/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-theme/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-theme/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-tool/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-tool/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-trajectory/lib/client.js +2 -2
- package/node_modules/@deepseek-ai/dsh-client-ui-trajectory/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-user-questions/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-user-questions/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-workflow-run/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-workflow-run/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/client.js +1 -1
- package/node_modules/@deepseek-ai/dsh-client-ui-workspace/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-client-web/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-command-compact/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-command-feedback/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-command-goal/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-commands/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-compaction/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-compaction-basic/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-compaction-tool-result-pruner/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-cordis-client-runner/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-cordis-host-runner/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-credentials/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-credentials-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-fs/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-fs-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-fs-observation-policy/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-fs-sandbox/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-goal/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-goal-round-driver/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-headless/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-home-paths/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-host-apiproxy/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-host-directory-picker-auto/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-host-directory-picker-browse/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-host-directory-picker-native/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-host-frontend-static/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-host-plugin-inventory/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-jobs/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-jobs-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-llm/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-llm-deepseek/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-llm-pi-ai/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-llm-retry/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-mcp-client/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-message-feedback/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-permission-presets/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-plan-mode/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-pwsh-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-pwsh-sandbox/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-repeat-tool-reminder/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-sandbox-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-sandbox-policy/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-schedule/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-checkpoint-policy/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-log-export/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-persistence/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-persistence-jsonl/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-projection/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-projection-cache/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-query/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-query-sqlite/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-reference/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-stats/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-telemetry/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-telemetry-otel/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-title/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-title-first-prompt-llm/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-session-title-llm/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-settings/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-settings-file/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-shell/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-shell-env/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-skill/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-skill-filesystem/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-spill/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-spill-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-spill-policy/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-storage-domain/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-subagent/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-subagent-fork-in-process/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-subagent-in-process-driver/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-subagent-spawn-in-process/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-subprocess/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-subprocess-local/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-system-prompt/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-terminal/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-terminal-bash/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-time-context/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tmux-context/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-token-meter/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-bash/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-bash-persistent/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-call-timeout-policy/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-cordis/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-fs/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-fs-search/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-goal/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-jobs/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-pwsh/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-ralph/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-skill/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-str-replace-editor/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-subagent/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-subagent-control/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-subagent-report/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-todo/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-web/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tool-workflow/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-tools/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-typert-loader/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-typert-protocol/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-user-approval/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-web/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-web-app/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-web-search-deepseek/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-workflow/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-workflow-worker-thread/package.json +1 -1
- package/node_modules/@deepseek-ai/dsh-workspace/package.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
# Playbook Guide
|
|
2
|
+
|
|
3
|
+
> Complete reference for recording, generating, editing, and replaying `qqbrowser-skill` playbooks.
|
|
4
|
+
>
|
|
5
|
+
> Load this file when the user asks to **record**, **save**, **reuse**, **generate**, **edit**, or **run** a browser automation playbook.
|
|
6
|
+
|
|
7
|
+
## Scope
|
|
8
|
+
|
|
9
|
+
A playbook is a parameterized JSON script generated from a real browser task recording. It lets future runs replay the same browser workflow without AI deciding every click again.
|
|
10
|
+
|
|
11
|
+
This guide covers the full Branch B lifecycle:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
explicit user request to record/save/reuse
|
|
15
|
+
↓
|
|
16
|
+
task_begin / browser operations / task_end
|
|
17
|
+
↓
|
|
18
|
+
task_latest
|
|
19
|
+
↓
|
|
20
|
+
convert recording into playbook JSON
|
|
21
|
+
↓
|
|
22
|
+
save under ~/.qqbrowser-skill/playbooks/
|
|
23
|
+
↓
|
|
24
|
+
verify safely with browser_replay
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Main workflow constraints from `SKILL.md` still apply:
|
|
28
|
+
|
|
29
|
+
- Start `browser_start_session` before any browser operation.
|
|
30
|
+
- Run `playbook_list` before manual automation.
|
|
31
|
+
- Enter Branch B only when the user explicitly asks to record/save/reuse.
|
|
32
|
+
- End with `browser_end_session` even on failure.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Branch B Recording Rules
|
|
37
|
+
|
|
38
|
+
> These rules apply **only inside `task_begin` / `task_end`**. They exist to make the recording clean enough to become a reusable playbook.
|
|
39
|
+
>
|
|
40
|
+
> In one-off Branch C tasks, exploratory trial-and-error, hardcoded indices, ad-hoc selectors, and multiple probes are acceptable because no reusable playbook will be generated.
|
|
41
|
+
|
|
42
|
+
### Rule 1: Task Classification
|
|
43
|
+
|
|
44
|
+
| Category | Replayable? | Strategy |
|
|
45
|
+
|---|---:|---|
|
|
46
|
+
| **A: Fixed-path** — navigate, click, fill | ✅ | Standard replayable browser commands |
|
|
47
|
+
| **B: Data extraction** | ⚠️ | Use `browser_eval_content_js` for replayable structured output |
|
|
48
|
+
| **C: Content understanding** — summarization, judgment, comparison | ❌ | Set `metadata.requires_ai: true`; include only replayable portions |
|
|
49
|
+
| **D: Dynamic iteration** — lists, search results, pagination | ⚠️ | Record stable selector and enough repeated iterations to convert to `loop` |
|
|
50
|
+
|
|
51
|
+
### Rule 2: Data Extraction — Prefer JS in Recordings
|
|
52
|
+
|
|
53
|
+
Inside `task_begin` / `task_end`, extract data with `browser_eval_content_js`, not `browser_snapshot --markdown`.
|
|
54
|
+
|
|
55
|
+
`browser_snapshot --markdown` is useful for AI reading in one-off tasks, but it is not a replayable structured extraction step.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# ✅ Replayable structured extraction
|
|
59
|
+
browser_eval_content_js --script "JSON.stringify(Array.from(document.querySelectorAll('.item')).slice(0,10).map(el=>({title:el.querySelector('.title')?.textContent?.trim()})))"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Rule 3: Analyze First, Execute Once
|
|
63
|
+
|
|
64
|
+
Do not perform trial-and-error inside a recording. Use `browser_snapshot` to inspect the page, then execute one definitive operation.
|
|
65
|
+
|
|
66
|
+
**Prohibited inside `task_begin` / `task_end`:**
|
|
67
|
+
|
|
68
|
+
- Multiple `browser_eval_content_js` attempts with different guessed selectors.
|
|
69
|
+
- Hardcoding element indices inside JavaScript scripts.
|
|
70
|
+
- Using regular expressions on page text as the main selector strategy.
|
|
71
|
+
|
|
72
|
+
If the recorded path is wrong, call `task_end`, discard the bad recording, and re-record cleanly.
|
|
73
|
+
|
|
74
|
+
### Rule 4: JS Selector Priority
|
|
75
|
+
|
|
76
|
+
When writing extraction scripts, choose selectors in this order:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
id > data-* > ARIA > semantic class > structural path
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Never use dynamic or hashed classes such as `.css-1a2b3c` unless no stable alternative exists and the playbook is marked `requires_ai` or requires manual maintenance.
|
|
83
|
+
|
|
84
|
+
### Rule 5: Loop Recording
|
|
85
|
+
|
|
86
|
+
When the task involves repeated list items, record **at least 2 iterations** using a stable selector and `browser_find_and_act --by css --nth`.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Discover selector first.
|
|
90
|
+
# <index-from-snapshot> must be copied from the latest browser_snapshot output.
|
|
91
|
+
browser_get_info --type list_selector --index "<index-from-snapshot>"
|
|
92
|
+
|
|
93
|
+
# Record a probe for the playbook generator.
|
|
94
|
+
browser_eval_content_js --script "JSON.stringify({__list_probe__: true, selector: '.result-item h2 a', count: document.querySelectorAll('.result-item h2 a').length, samples: Array.from(document.querySelectorAll('.result-item h2 a')).slice(0,3).map(e=>e.textContent.trim())})"
|
|
95
|
+
|
|
96
|
+
# Iteration 1
|
|
97
|
+
browser_find_and_act --by css --value ".result-item h2 a" --action click --nth 1
|
|
98
|
+
# ... extract, then return to list or close/switch tab ...
|
|
99
|
+
|
|
100
|
+
# Iteration 2
|
|
101
|
+
browser_find_and_act --by css --value ".result-item h2 a" --action click --nth 2
|
|
102
|
+
# ... extract, then return to list or close/switch tab ...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Key rules:**
|
|
106
|
+
|
|
107
|
+
- Use `browser_get_info --type list_selector` to discover a list-level CSS selector. Never guess selectors.
|
|
108
|
+
- Use `browser_find_and_act --by css --value "<selector>" --nth N` for list items.
|
|
109
|
+
- Avoid `browser_click_element --index ...` for dynamic list iteration; indices are snapshot-specific and not stable across replay.
|
|
110
|
+
- Keep the `__list_probe__` result available to the generator; it is the strongest signal for selector extraction and loop conversion.
|
|
111
|
+
|
|
112
|
+
### Rule 6: Multi-Tab Recording
|
|
113
|
+
|
|
114
|
+
When links open new tabs:
|
|
115
|
+
|
|
116
|
+
1. After click, use `browser_tab_list` to identify the new tab.
|
|
117
|
+
2. Use `browser_tab_switch` → extract/operate → `browser_tab_close` → switch back.
|
|
118
|
+
3. Do not use `browser_go_back` for cross-tab navigation.
|
|
119
|
+
4. Record 2+ iterations if this is part of a loop.
|
|
120
|
+
|
|
121
|
+
### Rule 7: Fixed vs Variable Parameters
|
|
122
|
+
|
|
123
|
+
| Fixed — never parameterize | Variable — replace with `{{param}}` |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `index`, `action`, fixed base URLs, fixed UI buttons, `settings` | user text, emails, usernames, quantities, query params, file paths, user-specific URL segments, `actionValue` |
|
|
126
|
+
|
|
127
|
+
### Rule 8: Non-Replayable Commands Are Allowed During Recording
|
|
128
|
+
|
|
129
|
+
These commands are useful while recording because the AI needs to inspect the page, but they must be filtered out when generating final replay steps:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
browser_snapshot
|
|
133
|
+
browser_screenshot
|
|
134
|
+
browser_get_info
|
|
135
|
+
browser_check_state
|
|
136
|
+
browser_get_dropdown_options
|
|
137
|
+
browser_tab_list
|
|
138
|
+
task_begin / task_end / task_latest
|
|
139
|
+
browser_done
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`browser_eval_content_js` is replayable and is not filtered by default. If it is only a selector probe, it may still be useful as a harmless validation/extraction step; otherwise the generator can use its output to build a cleaner loop step.
|
|
143
|
+
|
|
144
|
+
### Rule 9: Prefer Semantic Locators for Dynamic Content
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# ✅ Stable across page changes when text is unique
|
|
148
|
+
browser_find_and_act --by text --value "{{target}}" --action click
|
|
149
|
+
|
|
150
|
+
# ⚠️ Only for stable structures; index must come from latest snapshot
|
|
151
|
+
browser_click_element --index "<index-from-latest-snapshot>"
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Use `browser_find_and_act` whenever the target can be described by text, label, placeholder, role, test id, or stable CSS.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## From Recording to Playbook
|
|
159
|
+
|
|
160
|
+
After `task_end`, raw recordings are **not** replay-ready. Convert them into a parameterized playbook.
|
|
161
|
+
|
|
162
|
+
### Workflow
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# 1. Load the most recent recording
|
|
166
|
+
qqbrowser-skill task_latest
|
|
167
|
+
|
|
168
|
+
# 2. AI analyzes the recording and generates a playbook:
|
|
169
|
+
# 2a: Filter excluded actions
|
|
170
|
+
# 2b: Extract selector signals such as __list_probe__
|
|
171
|
+
# 2c: Detect repeated sequences and convert them to loop
|
|
172
|
+
# 2d: Detect tab operations and convert physical tabId to semantic tab refs
|
|
173
|
+
# 2e: Cross-check loop + tab behavior
|
|
174
|
+
# 2f: Parameterize user data only
|
|
175
|
+
# 2g: Add metadata, params, settings, and descriptions
|
|
176
|
+
|
|
177
|
+
# 3. Save
|
|
178
|
+
mkdir -p ~/.qqbrowser-skill/playbooks
|
|
179
|
+
# Write JSON to ~/.qqbrowser-skill/playbooks/<kebab-case-name>.json
|
|
180
|
+
|
|
181
|
+
# 4. Verify safely
|
|
182
|
+
qqbrowser-skill browser_replay --script ~/.qqbrowser-skill/playbooks/<name>.json \
|
|
183
|
+
--variables '{"param1": "value1"}'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Safe Verification
|
|
187
|
+
|
|
188
|
+
`browser_replay` executes all playbook steps sequentially in the browser and can take **up to 10 minutes**.
|
|
189
|
+
|
|
190
|
+
- Wait for the command to return. Do not interrupt, retry, or fall back to manual mode while it is still running.
|
|
191
|
+
- Inspect `success`, `failed_step`, and `step_results` in the response.
|
|
192
|
+
- If a step fails, fix the playbook JSON and re-run only when safe.
|
|
193
|
+
- For non-idempotent or side-effecting tasks — posting, submitting, messaging, purchasing, deleting, sending email — do **not** replay against a live target without explicit user approval. Prefer draft mode, sandbox/test data, or manual JSON inspection.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Critical Playbook Rules
|
|
198
|
+
|
|
199
|
+
1. A playbook **MUST** be generated from an actual `task_latest` recording. Never fabricate steps.
|
|
200
|
+
2. Use action names and stable replay parameters from the recording. Copy `index` values verbatim when they are part of fixed-path steps.
|
|
201
|
+
3. Only replace user-controlled data with `{{param_name}}`.
|
|
202
|
+
4. Never parameterize `action`, fixed workflow structure, `settings`, or element `index`.
|
|
203
|
+
5. Save playbooks to `~/.qqbrowser-skill/playbooks/<kebab-case-name>.json`.
|
|
204
|
+
6. JSON structure is `version` + `metadata` + `params` + `settings` + `steps`. The key is `params`, not `parameters`.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Valid Actions
|
|
209
|
+
|
|
210
|
+
### Allowed Replay Actions
|
|
211
|
+
|
|
212
|
+
| Action | Parameterizable Fields |
|
|
213
|
+
|---|---|
|
|
214
|
+
| `browser_go_to_url` | `url` |
|
|
215
|
+
| `browser_click_element` / `browser_dblclick_element` / `browser_focus_element` | — |
|
|
216
|
+
| `browser_input_text` / `browser_keyboard_op` | `text` |
|
|
217
|
+
| `browser_keypress` / `browser_keydown` / `browser_keyup` | — |
|
|
218
|
+
| `browser_select_dropdown_option` | `text` |
|
|
219
|
+
| `browser_check_op` | — |
|
|
220
|
+
| `browser_scroll_down` / `browser_scroll_up` / `browser_scroll_to_top` / `browser_scroll_to_bottom` / `browser_scroll_by` / `browser_scroll_into_view` | — |
|
|
221
|
+
| `browser_scroll_to_text` | `text` |
|
|
222
|
+
| `browser_wait` | — |
|
|
223
|
+
| `browser_go_back` | — |
|
|
224
|
+
| `browser_dialog` | `text` |
|
|
225
|
+
| `browser_tab_open` | `url`, `as` alias |
|
|
226
|
+
| `browser_tab_switch` / `browser_tab_close` | `tab` semantic ref |
|
|
227
|
+
| `browser_download_file` / `browser_download_url` | — |
|
|
228
|
+
| `browser_eval_content_js` | `script` |
|
|
229
|
+
| `browser_find_and_act` | `value`, `actionValue`, `name`, `nth`, `openInNewTab` |
|
|
230
|
+
| `loop` | `count`, `variable`, `start`, `on_error`, nested `steps` |
|
|
231
|
+
|
|
232
|
+
### Excluded Actions
|
|
233
|
+
|
|
234
|
+
Filter these out from final playbook steps:
|
|
235
|
+
|
|
236
|
+
```text
|
|
237
|
+
browser_snapshot
|
|
238
|
+
browser_screenshot
|
|
239
|
+
browser_get_info
|
|
240
|
+
browser_check_state
|
|
241
|
+
browser_get_dropdown_options
|
|
242
|
+
browser_tab_list
|
|
243
|
+
task_begin / task_end / task_latest
|
|
244
|
+
browser_done
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Playbook JSON Format
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"version": "1.0",
|
|
254
|
+
"metadata": {
|
|
255
|
+
"name": "小红书发布笔记",
|
|
256
|
+
"description": "自动打开小红书并发布一篇笔记",
|
|
257
|
+
"created_at": "2026-05-21T16:59:00Z",
|
|
258
|
+
"url": "https://www.xiaohongshu.com/explore",
|
|
259
|
+
"requires_ai": false
|
|
260
|
+
},
|
|
261
|
+
"params": {
|
|
262
|
+
"notes_title": { "description": "笔记标题", "required": true },
|
|
263
|
+
"notes_content": { "description": "笔记正文内容", "required": true }
|
|
264
|
+
},
|
|
265
|
+
"settings": {
|
|
266
|
+
"default_delay_ms": 500,
|
|
267
|
+
"default_retry_count": 1,
|
|
268
|
+
"step_timeout_ms": 30000
|
|
269
|
+
},
|
|
270
|
+
"steps": [
|
|
271
|
+
{
|
|
272
|
+
"action": "browser_go_to_url",
|
|
273
|
+
"params": { "url": "https://www.xiaohongshu.com/explore" },
|
|
274
|
+
"description": "打开小红书探索页"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"action": "browser_input_text",
|
|
278
|
+
"params": { "index": "6_2vl4_txwg", "text": "{{notes_title}}" },
|
|
279
|
+
"description": "输入笔记标题"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"action": "browser_click_element",
|
|
283
|
+
"params": { "index": "19_oxgq_ttmr" },
|
|
284
|
+
"description": "点击发布"
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Required Fields
|
|
291
|
+
|
|
292
|
+
- `version`: always `"1.0"`.
|
|
293
|
+
- `metadata.name`: human-readable name, Chinese is OK.
|
|
294
|
+
- `metadata.description`: one-line summary.
|
|
295
|
+
- `metadata.created_at`: ISO 8601 timestamp.
|
|
296
|
+
- `params`: parameter definitions; each param needs `description` and `required`, optional `default`.
|
|
297
|
+
- `settings`: replay defaults, usually `{ "default_delay_ms": 500, "default_retry_count": 1, "step_timeout_ms": 30000 }`.
|
|
298
|
+
- `steps`: ordered replay steps; each step needs `action`, `params`, and `description`.
|
|
299
|
+
|
|
300
|
+
### Optional Fields
|
|
301
|
+
|
|
302
|
+
- `metadata.url`: canonical entry URL.
|
|
303
|
+
- `metadata.requires_ai`: set `true` when some part still needs AI judgment.
|
|
304
|
+
- `condition`: skip a step unless an optional param exists.
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"action": "browser_input_text",
|
|
309
|
+
"params": { "index": "12_abcd_efgh", "text": "{{cc}}" },
|
|
310
|
+
"condition": { "param": "cc" },
|
|
311
|
+
"description": "填写抄送人"
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
When an optional param controls a sub-flow, add the same `condition` to **all** dependent steps.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Parameter Extraction
|
|
320
|
+
|
|
321
|
+
**Parameterize:**
|
|
322
|
+
|
|
323
|
+
- User-entered text.
|
|
324
|
+
- Emails, usernames, addresses, phone numbers.
|
|
325
|
+
- Search keywords and query params.
|
|
326
|
+
- Quantities such as `count`.
|
|
327
|
+
- File paths and user-specific URL segments.
|
|
328
|
+
- `actionValue` for `browser_find_and_act` fill/type operations.
|
|
329
|
+
|
|
330
|
+
**Never parameterize:**
|
|
331
|
+
|
|
332
|
+
- `action`.
|
|
333
|
+
- Element `index`.
|
|
334
|
+
- Fixed workflow URLs and fixed UI buttons.
|
|
335
|
+
- `settings`.
|
|
336
|
+
- Internal control structure unless explicitly supported, such as `loop.params.count`.
|
|
337
|
+
|
|
338
|
+
**Naming:** use descriptive `snake_case`, for example `notes_title` instead of `title`.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## `requires_ai` Tasks
|
|
343
|
+
|
|
344
|
+
Set `metadata.requires_ai: true` when the task contains AI-dependent operations that cannot be replayed deterministically, such as:
|
|
345
|
+
|
|
346
|
+
- Summarization.
|
|
347
|
+
- Comparison or subjective judgment.
|
|
348
|
+
- Conditional decisions based on page meaning.
|
|
349
|
+
- Rewriting content for another platform.
|
|
350
|
+
|
|
351
|
+
In that case, include only the deterministic browser portions as steps and leave the AI-dependent part outside the pure replay path.
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Selector Extraction for Loops
|
|
356
|
+
|
|
357
|
+
### Primary Source: `__list_probe__`
|
|
358
|
+
|
|
359
|
+
Look for `browser_eval_content_js` calls whose output contains `__list_probe__: true`.
|
|
360
|
+
|
|
361
|
+
```text
|
|
362
|
+
browser_eval_content_js → output:
|
|
363
|
+
{"__list_probe__": true, "selector": ".SearchResult-Card .ContentItem h2 a", "count": 10, "samples": ["文章A", "文章B", "文章C"]}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Extract the `selector` value and use it directly in loop `browser_find_and_act` steps.
|
|
367
|
+
|
|
368
|
+
### Fallback Order
|
|
369
|
+
|
|
370
|
+
If there is no `__list_probe__`:
|
|
371
|
+
|
|
372
|
+
1. If the recording already uses `browser_find_and_act --by css`, reuse that selector.
|
|
373
|
+
2. If the recording only uses `by: "text"` or `browser_click_element(index)`, the selector is unknown.
|
|
374
|
+
3. Use `browser_get_info --type list_selector --index <any_list_item_index>` to auto-detect a selector before generating the playbook.
|
|
375
|
+
4. If `list_selector` fails, run a focused `browser_eval_content_js` probe to discover a stable selector.
|
|
376
|
+
5. If all selector discovery fails, do not guess; set `metadata.requires_ai: true`.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Loop Steps
|
|
381
|
+
|
|
382
|
+
Use `loop` when the recording shows **2+ repetitions** of the same action sequence differing only in target item, and the count is user-controlled or naturally dynamic.
|
|
383
|
+
|
|
384
|
+
Do **not** use `loop` for fixed form fields or when a single `browser_eval_content_js` can extract all data at once.
|
|
385
|
+
|
|
386
|
+
### Loop JSON
|
|
387
|
+
|
|
388
|
+
```json
|
|
389
|
+
{
|
|
390
|
+
"action": "loop",
|
|
391
|
+
"params": { "count": "{{count}}", "variable": "i", "start": 1 },
|
|
392
|
+
"description": "逐个访问前N篇文章",
|
|
393
|
+
"steps": [ ... ]
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Fields:
|
|
398
|
+
|
|
399
|
+
- `count`: string or number; supports `{{param}}`.
|
|
400
|
+
- `variable`: loop variable name, usually `i`.
|
|
401
|
+
- `start`: default `1`; aligns with `nth` because `nth=1` is the first match.
|
|
402
|
+
- `on_error`: optional; default `"fail"`.
|
|
403
|
+
|
|
404
|
+
### `on_error` for Dynamic Lists
|
|
405
|
+
|
|
406
|
+
When the list length is unknown, use `on_error: "break"` with a large upper bound.
|
|
407
|
+
|
|
408
|
+
```json
|
|
409
|
+
{
|
|
410
|
+
"action": "loop",
|
|
411
|
+
"params": { "count": "100", "variable": "i", "start": 1, "on_error": "break" },
|
|
412
|
+
"description": "处理所有列表项(自动检测结束)",
|
|
413
|
+
"steps": [ ... ]
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
| `on_error` | Behavior |
|
|
418
|
+
|---|---|
|
|
419
|
+
| `"fail"` | Sub-step failure fails the whole replay |
|
|
420
|
+
| `"break"` | Sub-step failure exits the loop gracefully and replay continues |
|
|
421
|
+
|
|
422
|
+
Use `on_error: "break"` when the user asks to process all available items or when the count cannot be known at generation time.
|
|
423
|
+
|
|
424
|
+
### Loop Conversion Rules
|
|
425
|
+
|
|
426
|
+
1. Use `browser_find_and_act` with `by: "css"` + `nth: "{{i}}"` inside loops.
|
|
427
|
+
2. `by` must be `"css"` when `nth` is used. Do not use `by: "text"` or `by: "label"` for list iteration.
|
|
428
|
+
3. `value` must be a list-level CSS selector matching all items, not a specific title or keyword.
|
|
429
|
+
4. The selector must come from actual DOM: `__list_probe__`, existing `find_and_act(css)`, `list_selector`, or a live probe.
|
|
430
|
+
5. Convert repeated `browser_click_element(index=...)` on different list items into one `browser_find_and_act(css, nth="{{i}}")` step.
|
|
431
|
+
6. Convert repeated `find_and_act(by="text", value="标题A/B/C")` into `find_and_act(by="css", nth="{{i}}")`.
|
|
432
|
+
7. Include `browser_wait` between iterations for stability.
|
|
433
|
+
|
|
434
|
+
### Loop Decision Table
|
|
435
|
+
|
|
436
|
+
| Scenario | Method |
|
|
437
|
+
|---|---|
|
|
438
|
+
| List page data only | Single `browser_eval_content_js` |
|
|
439
|
+
| Need detail page content | `loop` |
|
|
440
|
+
| Pagination + extraction | `loop` |
|
|
441
|
+
| Fixed different form fields | Linear steps |
|
|
442
|
+
| Process all unknown-length items | `loop` with `on_error: "break"` and large `count` |
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## Tab Management
|
|
447
|
+
|
|
448
|
+
Playbooks must use **semantic tab references**. Physical `tabId` values change across sessions and must not be hardcoded.
|
|
449
|
+
|
|
450
|
+
| Reference | Meaning |
|
|
451
|
+
|---|---|
|
|
452
|
+
| `"origin"` | Tab active when replay started |
|
|
453
|
+
| `"current"` | Currently active tab |
|
|
454
|
+
| `"latest"` | Most recently opened tab |
|
|
455
|
+
| Custom alias | Named via `"as"` in `browser_tab_open` |
|
|
456
|
+
|
|
457
|
+
Examples:
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{ "action": "browser_tab_open", "params": { "url": "https://example.com", "as": "detail" }, "description": "打开详情页" }
|
|
461
|
+
{ "action": "browser_tab_switch", "params": { "tab": "origin" }, "description": "切回原始Tab" }
|
|
462
|
+
{ "action": "browser_tab_close", "params": { "tab": "current" }, "description": "关闭当前Tab" }
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Rules:
|
|
466
|
+
|
|
467
|
+
1. Never use physical `tabId` in playbook JSON.
|
|
468
|
+
2. `browser_go_back` does not work across tabs.
|
|
469
|
+
3. After `browser_tab_close`, explicitly `browser_tab_switch`; do not assume Chrome's active tab.
|
|
470
|
+
4. If a loop iteration opens a new tab, use `tab_switch("latest")` → operate/extract → `tab_close("current")` → `tab_switch("origin")`.
|
|
471
|
+
5. Use `browser_go_back` only if iteration stays in the same tab.
|
|
472
|
+
|
|
473
|
+
### `go_back` vs Tab Close/Switch
|
|
474
|
+
|
|
475
|
+
| Scenario | Method |
|
|
476
|
+
|---|---|
|
|
477
|
+
| Link opens in same tab | `browser_go_back` |
|
|
478
|
+
| Link opens in new tab | `browser_tab_close` + `browser_tab_switch` |
|
|
479
|
+
| Unsure, especially in loops | `openInNewTab: true` + `browser_tab_close` + `browser_tab_switch` |
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## `browser_find_and_act` Usage
|
|
484
|
+
|
|
485
|
+
For single-element targeting, use stable semantic locators:
|
|
486
|
+
|
|
487
|
+
```json
|
|
488
|
+
{ "action": "browser_find_and_act", "params": { "by": "text", "value": "提交", "action": "click" }, "description": "点击提交" }
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
For list iteration, use CSS + `nth`:
|
|
492
|
+
|
|
493
|
+
```json
|
|
494
|
+
{ "action": "browser_find_and_act", "params": { "by": "css", "value": ".List-item a", "nth": "{{i}}", "action": "click" }, "description": "点击第{{i}}个结果" }
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### `openInNewTab`
|
|
498
|
+
|
|
499
|
+
When `action: "click"` and the workflow requires a new tab, add `"openInNewTab": true`.
|
|
500
|
+
|
|
501
|
+
```json
|
|
502
|
+
{
|
|
503
|
+
"action": "browser_find_and_act",
|
|
504
|
+
"params": {
|
|
505
|
+
"by": "css",
|
|
506
|
+
"value": ".List-item a",
|
|
507
|
+
"nth": "{{i}}",
|
|
508
|
+
"action": "click",
|
|
509
|
+
"openInNewTab": true
|
|
510
|
+
},
|
|
511
|
+
"description": "在新Tab打开第{{i}}篇文章"
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Use it when:
|
|
516
|
+
|
|
517
|
+
- Loop pattern requires `tab_switch("latest")` → extract → `tab_close("current")` → `tab_switch("origin")`.
|
|
518
|
+
- Link behavior varies by site or context.
|
|
519
|
+
- Navigating away from the current page would break the workflow.
|
|
520
|
+
|
|
521
|
+
Behavior: the engine extracts the element's `href` and opens it via browser tab creation. If no `href` is found, it falls back to modifier-click behavior.
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## `browser_eval_content_js` Usage
|
|
526
|
+
|
|
527
|
+
Use `browser_eval_content_js` as the primary data extraction method for playbooks.
|
|
528
|
+
|
|
529
|
+
- Return JSON strings for structured data.
|
|
530
|
+
- Support `{{param}}` in scripts for selectors, keywords, counts, and user data.
|
|
531
|
+
- Prefer stable selectors: `id`, `data-*`, ARIA, semantic class, structural path.
|
|
532
|
+
- Avoid dynamic or hashed classes.
|
|
533
|
+
|
|
534
|
+
Example:
|
|
535
|
+
|
|
536
|
+
```json
|
|
537
|
+
{
|
|
538
|
+
"action": "browser_eval_content_js",
|
|
539
|
+
"params": {
|
|
540
|
+
"script": "JSON.stringify(Array.from(document.querySelectorAll('.product-item')).slice(0, Number('{{count}}')).map(el => ({ name: el.querySelector('.name')?.textContent?.trim(), price: el.querySelector('.price')?.textContent?.trim() })))"
|
|
541
|
+
},
|
|
542
|
+
"description": "提取商品列表"
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## Complete Example: Loop + New Tab
|
|
549
|
+
|
|
550
|
+
This example demonstrates key conversions: text target to CSS selector, physical tab ID to semantic tab refs, and repeated linear steps to `loop`.
|
|
551
|
+
|
|
552
|
+
Raw recording pattern:
|
|
553
|
+
|
|
554
|
+
```text
|
|
555
|
+
browser_eval_content_js → {"__list_probe__": true, "selector": ".ContentItem h2 a", "count": 10, "samples": ["文章标题A", "文章标题B", "文章标题C"]}
|
|
556
|
+
browser_find_and_act(by="text", value="文章标题A", action="click")
|
|
557
|
+
browser_click_element(index="197_3i4c_uv7n")
|
|
558
|
+
browser_tab_switch(tabId=1412776911)
|
|
559
|
+
browser_eval_content_js(script="...")
|
|
560
|
+
browser_tab_close(tabId=1412776911)
|
|
561
|
+
browser_find_and_act(by="text", value="文章标题B", action="click")
|
|
562
|
+
browser_click_element(index="206_90r2_rddo")
|
|
563
|
+
browser_tab_switch(tabId=1412776913)
|
|
564
|
+
browser_eval_content_js(script="...")
|
|
565
|
+
browser_tab_close(tabId=1412776913)
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
Generated playbook:
|
|
569
|
+
|
|
570
|
+
```json
|
|
571
|
+
{
|
|
572
|
+
"version": "1.0",
|
|
573
|
+
"metadata": {
|
|
574
|
+
"name": "搜索并提取文章(新Tab场景)",
|
|
575
|
+
"description": "搜索主题,逐个在新Tab打开文章提取内容",
|
|
576
|
+
"created_at": "2026-06-02T15:00:00Z",
|
|
577
|
+
"requires_ai": false
|
|
578
|
+
},
|
|
579
|
+
"params": {
|
|
580
|
+
"topic": { "description": "搜索关键词", "required": true },
|
|
581
|
+
"count": { "description": "提取数量", "required": false, "default": "3" }
|
|
582
|
+
},
|
|
583
|
+
"settings": {
|
|
584
|
+
"default_delay_ms": 500,
|
|
585
|
+
"default_retry_count": 1,
|
|
586
|
+
"step_timeout_ms": 30000
|
|
587
|
+
},
|
|
588
|
+
"steps": [
|
|
589
|
+
{
|
|
590
|
+
"action": "browser_go_to_url",
|
|
591
|
+
"params": { "url": "https://example.com/search?q={{topic}}" },
|
|
592
|
+
"description": "搜索主题"
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
"action": "browser_wait",
|
|
596
|
+
"params": { "seconds": 3 },
|
|
597
|
+
"description": "等待搜索结果"
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
"action": "loop",
|
|
601
|
+
"params": { "count": "{{count}}", "variable": "i", "start": 1 },
|
|
602
|
+
"description": "逐个在新Tab打开文章并提取",
|
|
603
|
+
"steps": [
|
|
604
|
+
{
|
|
605
|
+
"action": "browser_find_and_act",
|
|
606
|
+
"params": { "by": "css", "value": ".ContentItem h2 a", "nth": "{{i}}", "action": "click", "openInNewTab": true },
|
|
607
|
+
"description": "在新Tab打开第{{i}}篇文章"
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
"action": "browser_tab_switch",
|
|
611
|
+
"params": { "tab": "latest" },
|
|
612
|
+
"description": "切到文章Tab"
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
"action": "browser_wait",
|
|
616
|
+
"params": { "seconds": 2 },
|
|
617
|
+
"description": "等待加载"
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"action": "browser_eval_content_js",
|
|
621
|
+
"params": { "script": "JSON.stringify({title:document.querySelector('h1')?.textContent?.trim(),content:document.querySelector('.RichText')?.textContent?.substring(0,2000)})" },
|
|
622
|
+
"description": "提取内容"
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
"action": "browser_tab_close",
|
|
626
|
+
"params": { "tab": "current" },
|
|
627
|
+
"description": "关闭文章Tab"
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
"action": "browser_tab_switch",
|
|
631
|
+
"params": { "tab": "origin" },
|
|
632
|
+
"description": "切回列表页"
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
"action": "browser_wait",
|
|
636
|
+
"params": { "seconds": 1 },
|
|
637
|
+
"description": "等待恢复"
|
|
638
|
+
}
|
|
639
|
+
]
|
|
640
|
+
}
|
|
641
|
+
]
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
Key conversions:
|
|
646
|
+
|
|
647
|
+
- `by: "text"` + specific title → `by: "css"` + `nth: "{{i}}"`.
|
|
648
|
+
- `browser_click_element(index=...)` on repeated list items → merged into `browser_find_and_act`.
|
|
649
|
+
- `tabId: N` → `"latest"`, `"current"`, `"origin"`.
|
|
650
|
+
- 2+ repeated linear sequences → one `loop`.
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## Common Mistakes
|
|
655
|
+
|
|
656
|
+
| Mistake | Fix |
|
|
657
|
+
|---|---|
|
|
658
|
+
| Generating a playbook without `task_latest` | Always base playbook on an actual recording |
|
|
659
|
+
| Including `browser_snapshot` or `browser_get_info` in final steps | Filter them out; they are AI-decision/probe commands |
|
|
660
|
+
| Parameterizing `index` or `action` | Only parameterize user-controlled values |
|
|
661
|
+
| Using `parameters` instead of `params` | Schema key is `params` |
|
|
662
|
+
| Expanding N repetitions as linear steps | Use `loop` with `{{count}}` or `on_error: "break"` |
|
|
663
|
+
| Using `click_element` + hardcoded index in loops | Use `find_and_act` + CSS selector + `nth` |
|
|
664
|
+
| Using `by: "text"` or `by: "label"` with `nth` | `nth` requires `by: "css"` |
|
|
665
|
+
| Keeping specific titles in loop locators | Convert title-specific actions to CSS + `nth` |
|
|
666
|
+
| Guessing CSS selectors | Use `__list_probe__`, existing CSS locator, `list_selector`, or live probe |
|
|
667
|
+
| Using search keyword as loop `value` | `value` must be a CSS selector, not the keyword |
|
|
668
|
+
| Hardcoding physical `tabId` | Use semantic refs: `origin`, `current`, `latest`, or custom alias |
|
|
669
|
+
| Using `browser_go_back` after a new tab opens | Use `browser_tab_close` + `browser_tab_switch` |
|
|
670
|
+
| Dropping tab operations from a recording | Preserve tab behavior with semantic refs |
|
|
671
|
+
| Replaying a side-effecting playbook blindly | Use test data, draft mode, or explicit user approval |
|