@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.
Files changed (200) hide show
  1. package/config/agent-presets/code/agent.cordis.yml +3 -0
  2. package/config/agent-presets/minimal/agent.cordis.yml +6 -1
  3. package/config/agent-presets/standard/agent.cordis.yml +3 -0
  4. package/config/skills/bilibili-video-download/SKILL.md +84 -0
  5. package/config/skills/bilibili-video-download/bilibili.webmcp.tools.js +53 -0
  6. package/config/skills/qqbrowser-use/SKILL.md +444 -0
  7. package/config/skills/qqbrowser-use/references/commands-extended.md +74 -0
  8. package/config/skills/qqbrowser-use/references/playbook.md +671 -0
  9. package/config/skills/qqbrowser-use/references/session-lifecycle.md +102 -0
  10. package/cordis.patch.yml +5 -0
  11. package/lib/client.js +1 -1
  12. package/lib/index.js +4 -1
  13. package/lib/sandbox-local.js +1 -0
  14. package/node_modules/@deepseek-ai/cordis/package.json +1 -1
  15. package/node_modules/@deepseek-ai/cordis-plugin-include/package.json +1 -1
  16. package/node_modules/@deepseek-ai/dsh-agent/package.json +1 -1
  17. package/node_modules/@deepseek-ai/dsh-agent-default-model/package.json +1 -1
  18. package/node_modules/@deepseek-ai/dsh-agent-instructions/package.json +1 -1
  19. package/node_modules/@deepseek-ai/dsh-agent-loop/package.json +1 -1
  20. package/node_modules/@deepseek-ai/dsh-agent-presets/package.json +1 -1
  21. package/node_modules/@deepseek-ai/dsh-anonymous-user-id/package.json +1 -1
  22. package/node_modules/@deepseek-ai/dsh-api-remotes/package.json +1 -1
  23. package/node_modules/@deepseek-ai/dsh-app-boot/package.json +1 -1
  24. package/node_modules/@deepseek-ai/dsh-attachment/package.json +1 -1
  25. package/node_modules/@deepseek-ai/dsh-attachment-local/package.json +1 -1
  26. package/node_modules/@deepseek-ai/dsh-base/package.json +1 -1
  27. package/node_modules/@deepseek-ai/dsh-bash-local/package.json +1 -1
  28. package/node_modules/@deepseek-ai/dsh-bash-sandbox/package.json +1 -1
  29. package/node_modules/@deepseek-ai/dsh-client-connection/package.json +1 -1
  30. package/node_modules/@deepseek-ai/dsh-client-hmr/package.json +1 -1
  31. package/node_modules/@deepseek-ai/dsh-client-locale/lib/client.js +1 -1
  32. package/node_modules/@deepseek-ai/dsh-client-locale/package.json +1 -1
  33. package/node_modules/@deepseek-ai/dsh-client-runtime/package.json +1 -1
  34. package/node_modules/@deepseek-ai/dsh-client-ui-agent-preset/lib/client.js +1 -1
  35. package/node_modules/@deepseek-ai/dsh-client-ui-agent-preset/package.json +1 -1
  36. package/node_modules/@deepseek-ai/dsh-client-ui-attachment/package.json +1 -1
  37. package/node_modules/@deepseek-ai/dsh-client-ui-commands/lib/client.js +1 -1
  38. package/node_modules/@deepseek-ai/dsh-client-ui-commands/package.json +1 -1
  39. package/node_modules/@deepseek-ai/dsh-client-ui-conversation/lib/client.js +5 -5
  40. package/node_modules/@deepseek-ai/dsh-client-ui-conversation/package.json +1 -1
  41. package/node_modules/@deepseek-ai/dsh-client-ui-cordis/lib/client.js +1 -1
  42. package/node_modules/@deepseek-ai/dsh-client-ui-cordis/package.json +1 -1
  43. package/node_modules/@deepseek-ai/dsh-client-ui-deliverables/lib/client.js +1 -1
  44. package/node_modules/@deepseek-ai/dsh-client-ui-deliverables/package.json +1 -1
  45. package/node_modules/@deepseek-ai/dsh-client-ui-directory-picker-browse/lib/client.js +1 -1
  46. package/node_modules/@deepseek-ai/dsh-client-ui-directory-picker-browse/package.json +1 -1
  47. package/node_modules/@deepseek-ai/dsh-client-ui-directory-picker-native/package.json +1 -1
  48. package/node_modules/@deepseek-ai/dsh-client-ui-goal/lib/client.js +1 -1
  49. package/node_modules/@deepseek-ai/dsh-client-ui-goal/package.json +1 -1
  50. package/node_modules/@deepseek-ai/dsh-client-ui-input-trigger/lib/client.js +1 -1
  51. package/node_modules/@deepseek-ai/dsh-client-ui-input-trigger/package.json +1 -1
  52. package/node_modules/@deepseek-ai/dsh-client-ui-jobs/lib/client.js +1 -1
  53. package/node_modules/@deepseek-ai/dsh-client-ui-jobs/package.json +1 -1
  54. package/node_modules/@deepseek-ai/dsh-client-ui-layout/lib/client.js +1 -1
  55. package/node_modules/@deepseek-ai/dsh-client-ui-layout/package.json +1 -1
  56. package/node_modules/@deepseek-ai/dsh-client-ui-message-feedback/lib/client.js +1 -1
  57. package/node_modules/@deepseek-ai/dsh-client-ui-message-feedback/package.json +1 -1
  58. package/node_modules/@deepseek-ai/dsh-client-ui-model-selection/lib/client.js +1 -1
  59. package/node_modules/@deepseek-ai/dsh-client-ui-model-selection/package.json +1 -1
  60. package/node_modules/@deepseek-ai/dsh-client-ui-permission-presets/lib/client.js +1 -1
  61. package/node_modules/@deepseek-ai/dsh-client-ui-permission-presets/package.json +1 -1
  62. package/node_modules/@deepseek-ai/dsh-client-ui-plan/lib/client.js +1 -1
  63. package/node_modules/@deepseek-ai/dsh-client-ui-plan/package.json +1 -1
  64. package/node_modules/@deepseek-ai/dsh-client-ui-settings/package.json +1 -1
  65. package/node_modules/@deepseek-ai/dsh-client-ui-settings-general/lib/client.js +1 -1
  66. package/node_modules/@deepseek-ai/dsh-client-ui-settings-general/package.json +1 -1
  67. package/node_modules/@deepseek-ai/dsh-client-ui-settings-models/lib/client.js +1 -1
  68. package/node_modules/@deepseek-ai/dsh-client-ui-settings-models/package.json +1 -1
  69. package/node_modules/@deepseek-ai/dsh-client-ui-settings-plugin-inventory/lib/client.js +1 -1
  70. package/node_modules/@deepseek-ai/dsh-client-ui-settings-plugin-inventory/package.json +1 -1
  71. package/node_modules/@deepseek-ai/dsh-client-ui-settings-plugins/lib/client.js +1 -1
  72. package/node_modules/@deepseek-ai/dsh-client-ui-settings-plugins/package.json +1 -1
  73. package/node_modules/@deepseek-ai/dsh-client-ui-sidebar/lib/client.js +1 -1
  74. package/node_modules/@deepseek-ai/dsh-client-ui-sidebar/package.json +1 -1
  75. package/node_modules/@deepseek-ai/dsh-client-ui-skill/lib/client.js +1 -1
  76. package/node_modules/@deepseek-ai/dsh-client-ui-skill/package.json +1 -1
  77. package/node_modules/@deepseek-ai/dsh-client-ui-subagent/lib/client.js +1 -1
  78. package/node_modules/@deepseek-ai/dsh-client-ui-subagent/package.json +1 -1
  79. package/node_modules/@deepseek-ai/dsh-client-ui-theme/lib/client.js +1 -1
  80. package/node_modules/@deepseek-ai/dsh-client-ui-theme/package.json +1 -1
  81. package/node_modules/@deepseek-ai/dsh-client-ui-tool/lib/client.js +1 -1
  82. package/node_modules/@deepseek-ai/dsh-client-ui-tool/package.json +1 -1
  83. package/node_modules/@deepseek-ai/dsh-client-ui-trajectory/lib/client.js +2 -2
  84. package/node_modules/@deepseek-ai/dsh-client-ui-trajectory/package.json +1 -1
  85. package/node_modules/@deepseek-ai/dsh-client-ui-user-questions/lib/client.js +1 -1
  86. package/node_modules/@deepseek-ai/dsh-client-ui-user-questions/package.json +1 -1
  87. package/node_modules/@deepseek-ai/dsh-client-ui-workflow-run/lib/client.js +1 -1
  88. package/node_modules/@deepseek-ai/dsh-client-ui-workflow-run/package.json +1 -1
  89. package/node_modules/@deepseek-ai/dsh-client-ui-workspace/lib/client.js +1 -1
  90. package/node_modules/@deepseek-ai/dsh-client-ui-workspace/package.json +1 -1
  91. package/node_modules/@deepseek-ai/dsh-client-web/package.json +1 -1
  92. package/node_modules/@deepseek-ai/dsh-command-compact/package.json +1 -1
  93. package/node_modules/@deepseek-ai/dsh-command-feedback/package.json +1 -1
  94. package/node_modules/@deepseek-ai/dsh-command-goal/package.json +1 -1
  95. package/node_modules/@deepseek-ai/dsh-commands/package.json +1 -1
  96. package/node_modules/@deepseek-ai/dsh-compaction/package.json +1 -1
  97. package/node_modules/@deepseek-ai/dsh-compaction-basic/package.json +1 -1
  98. package/node_modules/@deepseek-ai/dsh-compaction-tool-result-pruner/package.json +1 -1
  99. package/node_modules/@deepseek-ai/dsh-cordis-client-runner/package.json +1 -1
  100. package/node_modules/@deepseek-ai/dsh-cordis-host-runner/package.json +1 -1
  101. package/node_modules/@deepseek-ai/dsh-credentials/package.json +1 -1
  102. package/node_modules/@deepseek-ai/dsh-credentials-local/package.json +1 -1
  103. package/node_modules/@deepseek-ai/dsh-fs/package.json +1 -1
  104. package/node_modules/@deepseek-ai/dsh-fs-local/package.json +1 -1
  105. package/node_modules/@deepseek-ai/dsh-fs-observation-policy/package.json +1 -1
  106. package/node_modules/@deepseek-ai/dsh-fs-sandbox/package.json +1 -1
  107. package/node_modules/@deepseek-ai/dsh-goal/package.json +1 -1
  108. package/node_modules/@deepseek-ai/dsh-goal-round-driver/package.json +1 -1
  109. package/node_modules/@deepseek-ai/dsh-headless/package.json +1 -1
  110. package/node_modules/@deepseek-ai/dsh-home-paths/package.json +1 -1
  111. package/node_modules/@deepseek-ai/dsh-host-apiproxy/package.json +1 -1
  112. package/node_modules/@deepseek-ai/dsh-host-directory-picker-auto/package.json +1 -1
  113. package/node_modules/@deepseek-ai/dsh-host-directory-picker-browse/package.json +1 -1
  114. package/node_modules/@deepseek-ai/dsh-host-directory-picker-native/package.json +1 -1
  115. package/node_modules/@deepseek-ai/dsh-host-frontend-static/package.json +1 -1
  116. package/node_modules/@deepseek-ai/dsh-host-plugin-inventory/package.json +1 -1
  117. package/node_modules/@deepseek-ai/dsh-jobs/package.json +1 -1
  118. package/node_modules/@deepseek-ai/dsh-jobs-local/package.json +1 -1
  119. package/node_modules/@deepseek-ai/dsh-llm/package.json +1 -1
  120. package/node_modules/@deepseek-ai/dsh-llm-deepseek/package.json +1 -1
  121. package/node_modules/@deepseek-ai/dsh-llm-pi-ai/package.json +1 -1
  122. package/node_modules/@deepseek-ai/dsh-llm-retry/package.json +1 -1
  123. package/node_modules/@deepseek-ai/dsh-mcp-client/package.json +1 -1
  124. package/node_modules/@deepseek-ai/dsh-message-feedback/package.json +1 -1
  125. package/node_modules/@deepseek-ai/dsh-permission-presets/package.json +1 -1
  126. package/node_modules/@deepseek-ai/dsh-plan-mode/package.json +1 -1
  127. package/node_modules/@deepseek-ai/dsh-pwsh-local/package.json +1 -1
  128. package/node_modules/@deepseek-ai/dsh-pwsh-sandbox/package.json +1 -1
  129. package/node_modules/@deepseek-ai/dsh-repeat-tool-reminder/package.json +1 -1
  130. package/node_modules/@deepseek-ai/dsh-sandbox-local/package.json +1 -1
  131. package/node_modules/@deepseek-ai/dsh-sandbox-policy/package.json +1 -1
  132. package/node_modules/@deepseek-ai/dsh-schedule/package.json +1 -1
  133. package/node_modules/@deepseek-ai/dsh-session/package.json +1 -1
  134. package/node_modules/@deepseek-ai/dsh-session-checkpoint-policy/package.json +1 -1
  135. package/node_modules/@deepseek-ai/dsh-session-log-export/package.json +1 -1
  136. package/node_modules/@deepseek-ai/dsh-session-persistence/package.json +1 -1
  137. package/node_modules/@deepseek-ai/dsh-session-persistence-jsonl/package.json +1 -1
  138. package/node_modules/@deepseek-ai/dsh-session-projection/package.json +1 -1
  139. package/node_modules/@deepseek-ai/dsh-session-projection-cache/package.json +1 -1
  140. package/node_modules/@deepseek-ai/dsh-session-query/package.json +1 -1
  141. package/node_modules/@deepseek-ai/dsh-session-query-sqlite/package.json +1 -1
  142. package/node_modules/@deepseek-ai/dsh-session-reference/package.json +1 -1
  143. package/node_modules/@deepseek-ai/dsh-session-stats/package.json +1 -1
  144. package/node_modules/@deepseek-ai/dsh-session-telemetry/package.json +1 -1
  145. package/node_modules/@deepseek-ai/dsh-session-telemetry-otel/package.json +1 -1
  146. package/node_modules/@deepseek-ai/dsh-session-title/package.json +1 -1
  147. package/node_modules/@deepseek-ai/dsh-session-title-first-prompt-llm/package.json +1 -1
  148. package/node_modules/@deepseek-ai/dsh-session-title-llm/package.json +1 -1
  149. package/node_modules/@deepseek-ai/dsh-settings/package.json +1 -1
  150. package/node_modules/@deepseek-ai/dsh-settings-file/package.json +1 -1
  151. package/node_modules/@deepseek-ai/dsh-shell/package.json +1 -1
  152. package/node_modules/@deepseek-ai/dsh-shell-env/package.json +1 -1
  153. package/node_modules/@deepseek-ai/dsh-skill/package.json +1 -1
  154. package/node_modules/@deepseek-ai/dsh-skill-filesystem/package.json +1 -1
  155. package/node_modules/@deepseek-ai/dsh-spill/package.json +1 -1
  156. package/node_modules/@deepseek-ai/dsh-spill-local/package.json +1 -1
  157. package/node_modules/@deepseek-ai/dsh-spill-policy/package.json +1 -1
  158. package/node_modules/@deepseek-ai/dsh-storage-domain/package.json +1 -1
  159. package/node_modules/@deepseek-ai/dsh-subagent/package.json +1 -1
  160. package/node_modules/@deepseek-ai/dsh-subagent-fork-in-process/package.json +1 -1
  161. package/node_modules/@deepseek-ai/dsh-subagent-in-process-driver/package.json +1 -1
  162. package/node_modules/@deepseek-ai/dsh-subagent-spawn-in-process/package.json +1 -1
  163. package/node_modules/@deepseek-ai/dsh-subprocess/package.json +1 -1
  164. package/node_modules/@deepseek-ai/dsh-subprocess-local/package.json +1 -1
  165. package/node_modules/@deepseek-ai/dsh-system-prompt/package.json +1 -1
  166. package/node_modules/@deepseek-ai/dsh-terminal/package.json +1 -1
  167. package/node_modules/@deepseek-ai/dsh-terminal-bash/package.json +1 -1
  168. package/node_modules/@deepseek-ai/dsh-time-context/package.json +1 -1
  169. package/node_modules/@deepseek-ai/dsh-tmux-context/package.json +1 -1
  170. package/node_modules/@deepseek-ai/dsh-token-meter/package.json +1 -1
  171. package/node_modules/@deepseek-ai/dsh-tool-ask-user/package.json +1 -1
  172. package/node_modules/@deepseek-ai/dsh-tool-bash/package.json +1 -1
  173. package/node_modules/@deepseek-ai/dsh-tool-bash-persistent/package.json +1 -1
  174. package/node_modules/@deepseek-ai/dsh-tool-call-timeout-policy/package.json +1 -1
  175. package/node_modules/@deepseek-ai/dsh-tool-cordis/package.json +1 -1
  176. package/node_modules/@deepseek-ai/dsh-tool-fs/package.json +1 -1
  177. package/node_modules/@deepseek-ai/dsh-tool-fs-search/package.json +1 -1
  178. package/node_modules/@deepseek-ai/dsh-tool-goal/package.json +1 -1
  179. package/node_modules/@deepseek-ai/dsh-tool-jobs/package.json +1 -1
  180. package/node_modules/@deepseek-ai/dsh-tool-pwsh/package.json +1 -1
  181. package/node_modules/@deepseek-ai/dsh-tool-ralph/package.json +1 -1
  182. package/node_modules/@deepseek-ai/dsh-tool-skill/package.json +1 -1
  183. package/node_modules/@deepseek-ai/dsh-tool-str-replace-editor/package.json +1 -1
  184. package/node_modules/@deepseek-ai/dsh-tool-subagent/package.json +1 -1
  185. package/node_modules/@deepseek-ai/dsh-tool-subagent-control/package.json +1 -1
  186. package/node_modules/@deepseek-ai/dsh-tool-subagent-report/package.json +1 -1
  187. package/node_modules/@deepseek-ai/dsh-tool-todo/package.json +1 -1
  188. package/node_modules/@deepseek-ai/dsh-tool-web/package.json +1 -1
  189. package/node_modules/@deepseek-ai/dsh-tool-workflow/package.json +1 -1
  190. package/node_modules/@deepseek-ai/dsh-tools/package.json +1 -1
  191. package/node_modules/@deepseek-ai/dsh-typert-loader/package.json +1 -1
  192. package/node_modules/@deepseek-ai/dsh-typert-protocol/package.json +1 -1
  193. package/node_modules/@deepseek-ai/dsh-user-approval/package.json +1 -1
  194. package/node_modules/@deepseek-ai/dsh-web/package.json +1 -1
  195. package/node_modules/@deepseek-ai/dsh-web-app/package.json +1 -1
  196. package/node_modules/@deepseek-ai/dsh-web-search-deepseek/package.json +1 -1
  197. package/node_modules/@deepseek-ai/dsh-workflow/package.json +1 -1
  198. package/node_modules/@deepseek-ai/dsh-workflow-worker-thread/package.json +1 -1
  199. package/node_modules/@deepseek-ai/dsh-workspace/package.json +1 -1
  200. 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 |