@anweat/dsh-browser 0.1.17 → 0.2.0
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/README.md +266 -88
- package/assets/skills/dsh-browser/SKILL.md +120 -0
- package/assets/skills/dsh-browser/references/develop.md +48 -0
- package/assets/skills/dsh-browser/references/observe.md +91 -0
- package/assets/skills/dsh-browser/references/opencli.md +25 -0
- package/assets/skills/dsh-browser/references/recipes.md +149 -0
- package/assets/skills/dsh-browser/references/userscripts.md +44 -0
- package/cordis.patch.yml +11 -3
- package/lib/actions/act.d.ts +3 -0
- package/lib/actions/act.js +147 -0
- package/lib/actions/act.js.map +1 -0
- package/lib/actions/automation.d.ts +3 -0
- package/lib/actions/automation.js +356 -0
- package/lib/actions/automation.js.map +1 -0
- package/lib/actions/crawl.d.ts +3 -0
- package/lib/actions/crawl.js +28 -0
- package/lib/actions/crawl.js.map +1 -0
- package/lib/actions/errors.d.ts +53 -0
- package/lib/actions/errors.js +178 -0
- package/lib/actions/errors.js.map +1 -0
- package/lib/actions/index-view.d.ts +42 -0
- package/lib/actions/index-view.js +268 -0
- package/lib/actions/index-view.js.map +1 -0
- package/lib/actions/inspect.d.ts +3 -0
- package/lib/actions/inspect.js +35 -0
- package/lib/actions/inspect.js.map +1 -0
- package/lib/actions/observe.d.ts +3 -0
- package/lib/actions/observe.js +121 -0
- package/lib/actions/observe.js.map +1 -0
- package/lib/actions/opencli.d.ts +3 -0
- package/lib/actions/opencli.js +51 -0
- package/lib/actions/opencli.js.map +1 -0
- package/lib/actions/registry.d.ts +35 -0
- package/lib/actions/registry.js +89 -0
- package/lib/actions/registry.js.map +1 -0
- package/lib/actions/run.d.ts +34 -0
- package/lib/actions/run.js +226 -0
- package/lib/actions/run.js.map +1 -0
- package/lib/actions/runtime.d.ts +3 -0
- package/lib/actions/runtime.js +33 -0
- package/lib/actions/runtime.js.map +1 -0
- package/lib/actions/schema.d.ts +31 -0
- package/lib/actions/schema.js +309 -0
- package/lib/actions/schema.js.map +1 -0
- package/lib/actions/script.d.ts +3 -0
- package/lib/actions/script.js +97 -0
- package/lib/actions/script.js.map +1 -0
- package/lib/actions/shared.d.ts +31 -0
- package/lib/actions/shared.js +45 -0
- package/lib/actions/shared.js.map +1 -0
- package/lib/actions/surface.d.ts +22 -0
- package/lib/actions/surface.js +18 -0
- package/lib/actions/surface.js.map +1 -0
- package/lib/actions/target.d.ts +3 -0
- package/lib/actions/target.js +66 -0
- package/lib/actions/target.js.map +1 -0
- package/lib/actions/types.d.ts +181 -0
- package/lib/actions/types.js +48 -0
- package/lib/actions/types.js.map +1 -0
- package/lib/activation-rules.d.ts +29 -0
- package/lib/activation-rules.js +33 -0
- package/lib/activation-rules.js.map +1 -0
- package/lib/approval-policy.d.ts +40 -2
- package/lib/approval-policy.js +184 -97
- package/lib/approval-policy.js.map +1 -1
- package/lib/automation-assets-rpc.d.ts +8 -2
- package/lib/automation-assets-rpc.js +68 -10
- package/lib/automation-assets-rpc.js.map +1 -1
- package/lib/automation-assets.d.ts +152 -4
- package/lib/automation-assets.js +255 -15
- package/lib/automation-assets.js.map +1 -1
- package/lib/automation-convert.d.ts +23 -0
- package/lib/automation-convert.js +110 -0
- package/lib/automation-convert.js.map +1 -0
- package/lib/automation-development.d.ts +26 -0
- package/lib/automation-development.js +47 -0
- package/lib/automation-development.js.map +1 -1
- package/lib/automation-execution.d.ts +57 -1
- package/lib/automation-execution.js +167 -11
- package/lib/automation-execution.js.map +1 -1
- package/lib/automation-journal.d.ts +92 -0
- package/lib/automation-journal.js +449 -0
- package/lib/automation-journal.js.map +1 -0
- package/lib/automation-v2.d.ts +138 -0
- package/lib/automation-v2.js +631 -0
- package/lib/automation-v2.js.map +1 -0
- package/lib/automation.d.ts +82 -5
- package/lib/automation.js +239 -82
- package/lib/automation.js.map +1 -1
- package/lib/browser-service.d.ts +309 -32
- package/lib/browser-service.js +636 -182
- package/lib/browser-service.js.map +1 -1
- package/lib/client/SettingsCard.js +95 -57
- package/lib/client/SettingsCard.js.map +1 -1
- package/lib/client/asset-editor.d.ts +55 -0
- package/lib/client/asset-editor.js +101 -0
- package/lib/client/asset-editor.js.map +1 -0
- package/lib/client/automation-assets-client.d.ts +99 -2
- package/lib/client/automation-assets-client.js +243 -10
- package/lib/client/automation-assets-client.js.map +1 -1
- package/lib/client/form.d.ts +48 -4
- package/lib/client/form.js +142 -29
- package/lib/client/form.js.map +1 -1
- package/lib/client/index.d.ts +25 -2
- package/lib/client/index.js +5 -1
- package/lib/client/index.js.map +1 -1
- package/lib/client/locales.d.ts +85 -6
- package/lib/client/locales.js +64 -12
- package/lib/client/locales.js.map +1 -1
- package/lib/client/prompts-form.d.ts +67 -0
- package/lib/client/prompts-form.js +140 -0
- package/lib/client/prompts-form.js.map +1 -0
- package/lib/client/prompts-status-client.d.ts +34 -0
- package/lib/client/prompts-status-client.js +53 -0
- package/lib/client/prompts-status-client.js.map +1 -0
- package/lib/client/settings-namespace.d.ts +1 -1
- package/lib/client/settings-namespace.js +1 -1
- package/lib/client/settings-namespace.js.map +1 -1
- package/lib/client.js +1915 -330
- package/lib/client.js.map +1 -1
- package/lib/config.d.ts +50 -1
- package/lib/config.js +31 -1
- package/lib/config.js.map +1 -1
- package/lib/dialogs.d.ts +37 -0
- package/lib/dialogs.js +73 -0
- package/lib/dialogs.js.map +1 -0
- package/lib/freedom.d.ts +23 -7
- package/lib/freedom.js +48 -59
- package/lib/freedom.js.map +1 -1
- package/lib/index.d.ts +5 -13
- package/lib/index.js +51 -30
- package/lib/index.js.map +1 -1
- package/lib/journal.d.ts +109 -0
- package/lib/journal.js +238 -0
- package/lib/journal.js.map +1 -0
- package/lib/locator.d.ts +82 -0
- package/lib/locator.js +190 -0
- package/lib/locator.js.map +1 -0
- package/lib/observe-script.d.ts +27 -0
- package/lib/observe-script.js +565 -0
- package/lib/observe-script.js.map +1 -0
- package/lib/observe.d.ts +179 -0
- package/lib/observe.js +368 -0
- package/lib/observe.js.map +1 -0
- package/lib/policy-keys.d.ts +30 -0
- package/lib/policy-keys.js +34 -0
- package/lib/policy-keys.js.map +1 -0
- package/lib/prompt-limits.d.ts +30 -0
- package/lib/prompt-limits.js +31 -0
- package/lib/prompt-limits.js.map +1 -0
- package/lib/prompts.d.ts +131 -0
- package/lib/prompts.js +295 -0
- package/lib/prompts.js.map +1 -0
- package/lib/skill.d.ts +100 -0
- package/lib/skill.js +181 -0
- package/lib/skill.js.map +1 -0
- package/lib/tool-defs.d.ts +72 -0
- package/lib/tool-defs.js +67 -0
- package/lib/tool-defs.js.map +1 -0
- package/lib/tools.d.ts +19 -4
- package/lib/tools.js +124 -922
- package/lib/tools.js.map +1 -1
- package/package.json +15 -11
- package/scripts/check-host-rpc.mjs +11 -4
- package/scripts/dump-prompts.mjs +29 -0
- package/scripts/measure-tool-surface.mjs +190 -0
- package/lib/settings-scope.d.ts +0 -56
- package/lib/settings-scope.js +0 -51
- package/lib/settings-scope.js.map +0 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsh-browser
|
|
3
|
+
description: Use to open, read, or operate web pages, and to reuse or develop browser automation assets (recipes, userscripts) with the dsh-browser tools browser_index and browser_call. 打开、读取、操作网页,或复用、开发浏览器自动化资产时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dsh-browser
|
|
7
|
+
|
|
8
|
+
Browser capabilities come through two tools. `browser_index` shows what exists; `browser_call` runs one action and returns `{ok, action, seq?, executionStatus, result | error{code,message,hint}}`. Actions are named `group.action`. Do not guess other tool names.
|
|
9
|
+
|
|
10
|
+
## 1. Reuse first
|
|
11
|
+
|
|
12
|
+
At the start of a task, search for an existing verified asset. If one fits, run it instead of exploring the site again.
|
|
13
|
+
|
|
14
|
+
```json call
|
|
15
|
+
{"action":"automation.search","args":{"query":"search issues","domain":"github.com"}}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```json call
|
|
19
|
+
{"action":"automation.run","args":{"id":"asset-id-from-search","url":"https://github.com/search","inputs":{"query":"dsh"}}}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Two calls, no schemas needed; only active assets are returned.
|
|
23
|
+
|
|
24
|
+
## 2. Finding actions
|
|
25
|
+
|
|
26
|
+
```json index
|
|
27
|
+
{}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
lists the groups and the environment state. `{"group":"act"}` lists a group, `{"action":"act.click"}` gives a schema, `{"query":"upload"}` searches. `automation.develop` has sub-actions; one sub-action's schema:
|
|
31
|
+
|
|
32
|
+
```json index
|
|
33
|
+
{"action":"automation.develop.save"}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Six calls you rarely need a schema for:
|
|
37
|
+
|
|
38
|
+
```json call
|
|
39
|
+
{"action":"target.open","args":{"url":"https://example.com/"}}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```json call
|
|
43
|
+
{"action":"observe.read","args":{}}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```json call
|
|
47
|
+
{"action":"act.click","args":{"locator":{"role":"button","name":"Search"}}}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```json call
|
|
51
|
+
{"action":"act.fill","args":{"locator":{"label":"Query"},"text":"dsh"}}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```json call
|
|
55
|
+
{"action":"act.wait","args":{"locator":{"text":"Results"}}}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A wrong argument returns `INVALID_ARGS` with the schema attached: fix and resend.
|
|
59
|
+
|
|
60
|
+
## 3. Locating elements
|
|
61
|
+
|
|
62
|
+
- Prefer a `locator` with `role` + `name`, then `label`, `text`, `testId`; a CSS `selector` only if nothing semantic exists.
|
|
63
|
+
- Locators are strict: one matching several elements does nothing and fails `LOCATOR_AMBIGUOUS` (`error.candidates` lists five). Narrow it (`exact`, a better name, `frame`/`framePath`); only if you must pick one, add `index` and `indexReason`.
|
|
64
|
+
- `observe.read` with `sections` (`controls`, `links`, `tables`; `references/observe.md`) gives checked locators for `act.*`; `ambiguous:true` means none is unique.
|
|
65
|
+
- Results carry `targetId` and `generation`. Pass `generation` as `expectGeneration` to `act.*`: if the page navigated since, it fails `TARGET_STALE` instead of acting.
|
|
66
|
+
- `act.fill` replaces, `act.type` presses keys, `act.clear` empties. A popup joins `target.list`; switch with `target.select`.
|
|
67
|
+
|
|
68
|
+
## 4. Side effects and failure
|
|
69
|
+
|
|
70
|
+
`act.*` change the page and may need the user's approval in `standard` mode. Read `error.code` and `error.hint`:
|
|
71
|
+
|
|
72
|
+
| code | what to do |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `INVALID_ARGS` | fix the arguments using the attached schema |
|
|
75
|
+
| `LOCATOR_NOT_FOUND` | read the page, adjust the locator, `act.wait` for dynamic content; nothing was done |
|
|
76
|
+
| `LOCATOR_AMBIGUOUS` | several matched, nothing was done: choose from `error.candidates` |
|
|
77
|
+
| `NOT_ACTIONABLE` | hidden, disabled, or covered: wait or dismiss the overlay; nothing was done |
|
|
78
|
+
| `TARGET_CLOSED` | call `target.open` again |
|
|
79
|
+
| `TARGET_STALE` | the page navigated (also when a read hit a navigation twice); nothing was done: `observe.read` again, use the new `generation` |
|
|
80
|
+
| `DEADLINE` | with `executionStatus: "outcome_unknown"` it may have taken effect: verify with `observe.read`, never resubmit blindly |
|
|
81
|
+
| `OUTCOME_UNKNOWN` | a page-changing step timed out mid-way: check with `observe.read` before any retry |
|
|
82
|
+
| `VALIDATION_FAILED` | the steps ran but an assert or postcondition did not hold: read the page, fix the recipe; do not repeat submits |
|
|
83
|
+
| `VALIDATION_MISSING` | a v2 draft has no `assert` or postcondition, so its test cannot pass: add one, save, test again |
|
|
84
|
+
| `CANCELLED` | stopped; steps that already ran stay done (see `effects`) |
|
|
85
|
+
| `POLICY_DENIED` / `CAPABILITY_UNAVAILABLE` | not allowed or not set up here (Chromium missing: `runtime.install`) |
|
|
86
|
+
|
|
87
|
+
Recipe runs (`automation.run_recipe`, `automation.run`, `automation.develop` test) return the whole run in `result` even when `ok` is false: `executionStatus`, `validationStatus` (`not_checked`, `passed`, `failed`), `completedSteps`, `failedStep`, `effects` (`none`, `observed`, `unknown`), `outputs`. `completed` with `not_checked` means the steps ran, not that the result is right; nothing is rolled back (`references/recipes.md`).
|
|
88
|
+
|
|
89
|
+
After a submit, purchase, or any irreversible step, confirm the result on the page (confirmation text, new URL, changed list) before telling the user it worked.
|
|
90
|
+
|
|
91
|
+
## 5. Choosing a path (cheapest first)
|
|
92
|
+
|
|
93
|
+
1. A saved asset: `automation.search` then `automation.run`.
|
|
94
|
+
2. OpenCLI adapters if the platform is covered: `opencli.catalog`, `opencli.run` (`references/opencli.md`).
|
|
95
|
+
3. Read-only extraction: `script.catalog`, `script.run_builtin`, a userscript (`references/userscripts.md`).
|
|
96
|
+
4. Page interaction: `target.open`, `observe.read`, `act.*` (`references/observe.md`).
|
|
97
|
+
5. Bounded multi-step work: `automation.run_recipe` (`references/recipes.md`); `crawl.crawl` for anonymous crawls.
|
|
98
|
+
|
|
99
|
+
## 6. Turning a success into an asset
|
|
100
|
+
|
|
101
|
+
When an exploration worked and will likely repeat, do not rewrite it by hand. Every page-touching call you made is in a session journal (its `seq` is in each reply); build the draft from it, then test it on other inputs:
|
|
102
|
+
|
|
103
|
+
```json call
|
|
104
|
+
{"action":"automation.develop","args":{"action":"draft_from_journal","name":"Keyword search","parameters":[{"seq":3,"field":"text","name":"keyword"}],"extract":[{"seq":7,"as":"items"}],"postconditions":[{"output":"items","allowEmpty":true},{"selector":"#status"}]}}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```json call
|
|
108
|
+
{"action":"automation.develop","args":{"action":"test","id":"draft-id","url":"https://example.com/search","inputSets":[{"keyword":"alpha"},{"keyword":"beta"}]}}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`parameters` turns a typed value into an input, `extract` names the `observe.read` that is the result, `postconditions` check it; failed calls and plain observations are left out. Read `unmapped` (what a recipe cannot express: the draft is then incomplete and cannot be activated), `warnings`, `pendingDisambiguation`. `inputSets` (2 to 5) run each in a fresh context and all must pass; `PARAMETERIZATION_SUSPECT` means different inputs gave the same output. Details: `references/develop.md`.
|
|
112
|
+
|
|
113
|
+
Write a recipe by hand (`save`, `validate`, `test`; schema v2 in `references/recipes.md`) only when the journal cannot cover it. `convert` turns a v1 asset into a v2 draft.
|
|
114
|
+
|
|
115
|
+
Every save is a new revision and a test vouches only for the revision it ran on. A draft is not usable until the user activates a tested revision; never claim it is ready. To repair an active asset, `{"action":"fork","id":...}` gives an editable copy. Never put cookies, tokens, passwords, or page content in an asset.
|
|
116
|
+
|
|
117
|
+
## 7. Boundaries
|
|
118
|
+
|
|
119
|
+
- The user's automationMode decides what is exposed and what asks for approval. Do not route around a refusal.
|
|
120
|
+
- Logged-in access uses a named AuthProfile (`authProfile` on `target.open`). Respect the site's rules and the usage buffer.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Developing a draft from an exploration
|
|
2
|
+
|
|
3
|
+
The shortest path from "it worked once" to a reusable asset: explore with `browser_call`, `draft_from_journal`, test with `inputSets`. The user activates; you never do.
|
|
4
|
+
|
|
5
|
+
## The journal
|
|
6
|
+
|
|
7
|
+
Every page-touching call (`target.*`, `observe.*`, `act.*`, `script.*`, `automation.run_recipe`, `automation.run`) is recorded in this session's journal and its reply carries `seq`. The journal keeps the last 200 calls, lives in memory only, and is dropped with the session. A call refused for bad arguments is not recorded; a failed call is, with its error code.
|
|
8
|
+
|
|
9
|
+
Each entry has the action, page `targetId`, `generationBefore` and `generationAfter`, the locator, scrubbed parameters, the outcome (`ok`, `executionStatus`, `errorCode`), `effects`, and a short summary. Typed text is never stored in an entry: `act.fill` and `act.type` text is a reference `{ref, length, sensitive?}`. The text itself is kept in memory only while it is short (200 characters or fewer) and does not look like a token or card number, and not at all when the page says the field is a password or the field is named like a secret. A `target.open` URL loses secret query values and userinfo. `observe.read` entries are observation points: they never become steps unless you name one in `extract`.
|
|
10
|
+
|
|
11
|
+
## draft_from_journal
|
|
12
|
+
|
|
13
|
+
```json call
|
|
14
|
+
{"action":"automation.develop","args":{"action":"draft_from_journal","name":"Keyword search","fromSeq":1,"toSeq":9,"exclude":[4],"parameters":[{"seq":3,"field":"text","name":"keyword","type":"string"}],"extract":[{"seq":8,"as":"items","mode":"text","dedupe":true}],"postconditions":[{"output":"items","allowEmpty":true},{"selector":"#status"}]}}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- `fromSeq`, `toSeq`: the slice (default: the whole journal). `exclude`: more seq to leave out. Failed calls and observations are already left out.
|
|
18
|
+
- `parameters`: `field` is `text` (act.fill, act.type), `values` (act.select) or `url` (a later target.open). Without it the typed text stays a literal, and `parameterCandidates` lists what you could parameterize. A value that was not kept (secret, too long) becomes a required input automatically (`SENSITIVE_VALUE_BECAME_INPUT`, or `VALUE_BECAME_INPUT` for a long value); pass it when testing and running.
|
|
19
|
+
- `extract`: `seq` of an `observe.read` that read the result (with a `locator`); `as` names the output; `mode` text, html or links; `dedupe` removes repeated lines (text) or elements (links). `extractCandidates` lists the scoped reads you could use.
|
|
20
|
+
- `postconditions`: written as given. Without them the reply only suggests some (`suggestedPostconditions`) and a test cannot pass (`NO_VERIFIER`). For a result that may legitimately be empty use `{"output":"items","allowEmpty":true}` together with a success marker such as `{"selector":"#status"}`: an empty result then passes, a missing container still fails.
|
|
21
|
+
- `id`: replace the draft of an earlier call (a new revision). Each call is one draft write; a session has a few (3 by default), so pass `postconditions` the first time.
|
|
22
|
+
- `domains` default to the hosts of the pages involved; `name` is required.
|
|
23
|
+
|
|
24
|
+
The reply: `steps`, `complete`, `sourceMap` (seq to step number; the opening `target.open` maps to 0: it is the `url` of test and run, `suggestedTestUrl`), `observationPoints`, `excluded` (with reasons), `unmapped`, `inputSchema`, `pendingDisambiguation` (steps that pick a match by `index`), `parameterCandidates`, `extractCandidates`, `suggestedPostconditions`, `domains`, `warnings`.
|
|
25
|
+
|
|
26
|
+
## What maps and what does not
|
|
27
|
+
|
|
28
|
+
Mapped: `act.click`, `hover`, `fill`, `type`, `clear`, `press`, `check`, `scroll`, `select` (one value), `act.wait` for a visible locator, plain URL text, network idle or a delay, a later `target.open` (a `goto`), a named `observe.read` (an `extract`). Unmapped, each with its reason: `script.*`, `act.upload`, `target.select` and any step on another page (a v2 recipe runs on one page), multi-value select, wait for hidden or detached, glob URL patterns, a frame chosen by name or url, a `target.open` whose URL carried a secret, a whole recipe run.
|
|
29
|
+
|
|
30
|
+
Any `unmapped` entry makes the draft `complete: false` (`INCOMPLETE_DRAFT`): it cannot be activated until you finish the recipe yourself and `save` it (that save replaces it). Warnings to act on: `GENERATION_JUMP` (the page moved between two calls without an action: add a wait or a goto), `EXCLUDED_NAVIGATION`, `NO_OPENING_STEP` (the test url must be the page the first step starts on), `FIXED_DELAY`, `NO_OUTPUT`, `FAILED_STEP_EXCLUDED` (the draft assumes later steps did not depend on it), `FAILED_STEP_EFFECTS_UNKNOWN`, `PARAMETER_NOT_APPLIED` and `EXTRACT_NOT_APPLIED` (the named seq is not a step), `JOURNAL_TRUNCATED` (the start of the exploration fell out of the 200 calls).
|
|
31
|
+
|
|
32
|
+
## Testing with inputSets
|
|
33
|
+
|
|
34
|
+
```json call
|
|
35
|
+
{"action":"automation.develop","args":{"action":"test","id":"draft-id","url":"https://example.com/search","inputSets":[{"keyword":"alpha"},{"keyword":"beta"}]}}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`inputSets` takes 2 to 5 input objects, all valid for the `inputSchema` and different from each other (checked before anything runs). Each runs in a new BrowserContext, never in your session page, with its own cookies; the first failing set stops the test. All sets must pass for the credential to be `passed`; it records how many sets ran and a digest of each set's inputs and outputs, never the values. The reply has one entry per set (`outputs`, `validationStatus`, `failedStep`), not repeated steps.
|
|
39
|
+
|
|
40
|
+
If different inputs gave identical outputs the test still passes but replies `warnings: [PARAMETERIZATION_SUSPECT]` with the set numbers, and the credential carries it: the input probably does not reach the page, or the output ignores it. Fix that before asking the user to activate. Without `inputSets` a test is one run on the session page, as before. An asset that declares inputs cannot be activated unless its passing test covered at least `automationAssets.minInputSetsForActivation` input sets (default 2): a single-input test cannot show the recipe does not hard-code that input, and activation is refused with `insufficient-input-sets`. Assets without inputs are not affected.
|
|
41
|
+
|
|
42
|
+
## The whole path
|
|
43
|
+
|
|
44
|
+
1. `automation.search`; reuse if an asset fits.
|
|
45
|
+
2. Explore: `target.open`, `observe.read`, `act.*`, `act.wait`, `observe.read` with a `locator` for the result.
|
|
46
|
+
3. `draft_from_journal` with `parameters`, `extract`, `postconditions`; read `unmapped` and `warnings`.
|
|
47
|
+
4. `test` with `inputSets` (include a keyword with no result when an empty result is legitimate).
|
|
48
|
+
5. Tell the user the draft and its tested revision; they activate it with that revision.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Reading what the browser returns
|
|
2
|
+
|
|
3
|
+
Load this when a page result is hard to interpret.
|
|
4
|
+
|
|
5
|
+
## observe.read and action results
|
|
6
|
+
|
|
7
|
+
`target.open`, `observe.read`, and the `act.*` actions return the page state in `result`:
|
|
8
|
+
|
|
9
|
+
- `url`: the current address (check it after a click to see whether navigation happened).
|
|
10
|
+
- `title`: the page title.
|
|
11
|
+
- `text`: readable page text, bounded. Long pages are cut; if the part you need is missing, scroll with `act.scroll` and read again, or extract it with a recipe or built-in script.
|
|
12
|
+
- `targetId` and `generation`: which page of your session this is and its version (see Page generations below).
|
|
13
|
+
- `screenshotPath`: a file path on the host (only when a screenshot was taken, for example by `target.open`).
|
|
14
|
+
|
|
15
|
+
A `truncation` field on the envelope means long text fields were shortened; the result is still valid JSON.
|
|
16
|
+
|
|
17
|
+
## Reading structure: sections
|
|
18
|
+
|
|
19
|
+
`observe.read` returns only `text` by default. Ask for more with `sections`, any of `content`, `controls`, `links`, `tables`:
|
|
20
|
+
|
|
21
|
+
```json call
|
|
22
|
+
{"action":"observe.read","args":{"sections":["controls"]}}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Narrow it to part of the page with a `locator` (everything below that element is read; it must match exactly one element) or a viewport `region`, and bound it with `maxItems` (per section) and `maxBytes` (whole result):
|
|
26
|
+
|
|
27
|
+
```json call
|
|
28
|
+
{"action":"observe.read","args":{"sections":["controls","tables"],"locator":{"role":"form"},"maxItems":20,"maxBytes":12000}}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Each control record has `role`, `name`, `type`, a `locator`, the `actions` that can work now, the states it has (`visible`, `disabled`, `readonly`, `checked`, `expanded`), `options` for a select or radio group, declared `constraints` (`required`, `min`, `max`, `step`, `pattern`, `minlength`, `maxlength`, `accept`, `multiple`), the browser's own `validity`, and a `source` (`dom` or `aria`). The full field list: `{"action":"observe.read.controls"}` in `browser_index`.
|
|
32
|
+
|
|
33
|
+
- The `locator` was checked against the live page: it matches that element and no other. Pass it unchanged to `act.*`. Inside an iframe it carries `framePath`; open shadow DOM needs nothing extra.
|
|
34
|
+
- `ambiguous: true`: nothing unique exists (two identical unlabeled buttons, say). `matches` says how many elements the locator hits and `nth` which one this is; acting on it as is returns `LOCATOR_AMBIGUOUS`. Pin it with `index: nth` and an `indexReason`, or scope the read to its container.
|
|
35
|
+
- A field that is missing was not observed or does not apply (a range input has no `readonly`); `false` always means observed and not so. An empty list is an answer: `options: []` is a select with no options, `rows: []` with `totalRows: 0` an empty table, `controls: []` a page without controls. A scope that does not exist is `LOCATOR_NOT_FOUND`.
|
|
36
|
+
- Values are not returned. `hasValue` says whether a field is empty. With `includeValues: true` plain fields return `value`, but never password, hidden, file, or token-like fields: those only carry `hasValue` and `sensitive: true`.
|
|
37
|
+
- `frames` lists the iframes searched. A cross-origin or sandboxed one has `crossOrigin: true` and its content is not observed, and `limits` says so; you can still reach into it with `framePath` if you know the element. Closed shadow roots cannot be read.
|
|
38
|
+
- `links` have `text`, absolute `href` (a secret-looking query value such as a token shows as `[redacted]`), and a `locator`. `tables` have `headers`, the first `maxItems` `rows`, `totalRows`, and `coverage`: `partial` (with a `reason`) means the page has more rows than the DOM shows, because of a virtualized list or a pager; scroll or use the pager, then read again.
|
|
39
|
+
- Output is cut at item boundaries to fit `maxBytes` and is always valid JSON. `truncation: {omittedBySection, reason}` names what is missing and `counts` how many the page has. Ask for one section, raise the limits, or scope with a `locator`. Details: `{"action":"observe.read.truncation"}`.
|
|
40
|
+
|
|
41
|
+
## Page generations
|
|
42
|
+
|
|
43
|
+
Every page state has a `generation`. It changes when the page's main frame navigates: a new page load, reload, history back or forward, `location` change, `history.pushState` or `replaceState`, a hash change. It does not change for a DOM re-render or for an iframe navigating on its own. Values only grow within your session and never repeat, so a `generation` cannot match a different page.
|
|
44
|
+
|
|
45
|
+
Pass the `generation` you observed as `expectGeneration` to an `act.*` call. If the page has moved on, the call fails with `TARGET_STALE` before doing anything, and `error.current` has the new `targetId` and `generation`: observe again and decide whether the page is still the one you meant.
|
|
46
|
+
|
|
47
|
+
```json call
|
|
48
|
+
{"action":"act.click","args":{"locator":{"role":"button","name":"Create account","exact":true},"expectGeneration":3}}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Recipes do not take it: they bind the page again on every run.
|
|
52
|
+
|
|
53
|
+
## Screenshots
|
|
54
|
+
|
|
55
|
+
```json call
|
|
56
|
+
{"action":"observe.screenshot","args":{"locator":{"role":"heading","name":"Pricing"}}}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
returns `{path}` inside `snapshotDir`. Without a locator it captures the full page. A `filename` must be a plain name ending in `.png`, `.jpg`, or `.jpeg`.
|
|
60
|
+
|
|
61
|
+
## Console and failed requests
|
|
62
|
+
|
|
63
|
+
Capture is off unless the page was opened with it:
|
|
64
|
+
|
|
65
|
+
```json call
|
|
66
|
+
{"action":"target.open","args":{"url":"https://example.com/","capture":["console","network"]}}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Then `inspect.console` (filter with `level`) and `inspect.requests` return bounded, redacted records: console text, and failed or HTTP 4xx/5xx requests (method, url, status). No bodies or headers are recorded. Records reset on the next `target.open`.
|
|
70
|
+
|
|
71
|
+
## Waiting
|
|
72
|
+
|
|
73
|
+
`act.wait` takes exactly one of: a `locator`/`selector` (with `state`), `urlPattern`, `networkIdle: true`, or `timeMs` (max 10000).
|
|
74
|
+
|
|
75
|
+
## Several pages
|
|
76
|
+
|
|
77
|
+
Each session has its own context; other sessions cannot see it. A link with `target=_blank` or `window.open` adds a popup to your page list without making it active. `target.list` shows every page (`id`, `url`, `title`, `active`); switch with `target.select`, then `observe.read` and `act.*` work on that page.
|
|
78
|
+
|
|
79
|
+
```json call
|
|
80
|
+
{"action":"target.list","args":{}}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```json call
|
|
84
|
+
{"action":"target.select","args":{"id":"t2"}}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Ids are never reused in a session. A popup that closed gives `TARGET_CLOSED` on select; list again. If the active page closes, the session falls back to another open page of the same context. `target.close` discards all of the session's pages.
|
|
88
|
+
|
|
89
|
+
## Typing and clearing
|
|
90
|
+
|
|
91
|
+
`act.fill` replaces a value at once (no key events). `act.type` presses the keys one by one, for autocompletes and masked inputs. `act.clear` empties a field and fires the input event, so controlled inputs update their state; do not fill an empty string to clear.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# OpenCLI site adapters
|
|
2
|
+
|
|
3
|
+
OpenCLI ships adapters for many platforms (for example Reddit, Xiaohongshu) that can be cheaper and more robust than driving pages by hand.
|
|
4
|
+
|
|
5
|
+
## Flow
|
|
6
|
+
|
|
7
|
+
1. `opencli.status` checks the daemon, extension, and Browser Bridge. Do not infer connectivity from `runtime.status` alone: it only shows the config switch.
|
|
8
|
+
2. `opencli.catalog` finds exact command names. Filter instead of dumping everything.
|
|
9
|
+
|
|
10
|
+
```json call
|
|
11
|
+
{"action":"opencli.catalog","args":{"site":"reddit","access":"read","limit":10}}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
3. `opencli.run` runs one command with verbatim arguments after `opencli`.
|
|
15
|
+
|
|
16
|
+
```json call
|
|
17
|
+
{"action":"opencli.run","args":{"args":["reddit","search","dsh","-f","json"]}}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Notes
|
|
21
|
+
|
|
22
|
+
- `access` in the catalog is the adapter's own declaration, not a guarantee. Treat `write` commands (post, delete) as irreversible and expect an approval prompt.
|
|
23
|
+
- `opencli.run` can reuse the logged-in Chrome profile, so it asks for approval unless the user chose `unrestricted`.
|
|
24
|
+
- Pass `-f json` where supported so the output is easy to read.
|
|
25
|
+
- Chinese community sites usually need a domain-scoped AuthProfile configured by the user; if a command reports a login problem, tell the user rather than trying to log in.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Recipes: bounded multi-step browser work
|
|
2
|
+
|
|
3
|
+
A recipe is 1 to 25 declarative steps run on the current page. Run one inline with `automation.run_recipe`, or save it as a draft with `automation.develop` so it can be tested, activated by the user, and reused with `automation.run`.
|
|
4
|
+
|
|
5
|
+
There are two schemas. **Write new recipes as v2** (`"schemaVersion":2`): locators are strict, so a step never silently acts on the wrong element. v1 (the default for an inline recipe without `schemaVersion`, and for every asset saved before v2) keeps its old behaviour: CSS `selector`, always the first match.
|
|
6
|
+
|
|
7
|
+
## v2 steps
|
|
8
|
+
|
|
9
|
+
Each step has a `type` and a `locator` where it acts on an element. A locator is one of `{"role":"button","name":"Save"}` (optional `exact`), `{"label":"Query"}`, `{"text":"More"}`, `{"testId":"save"}`, `{"css":"#id"}`, plus optional `framePath` (iframe selectors, outermost first).
|
|
10
|
+
|
|
11
|
+
| type | fields |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `wait` | `condition`: `locator` (with `locator`), `text` or `url` (with `value`), `load`, or `time` (with `waitMs`); optional `timeoutMs` |
|
|
14
|
+
| `click`, `hover`, `clear` | `locator` |
|
|
15
|
+
| `fill` | `locator`, `value` (non-empty; to set `""` write `allowEmpty: true`, or use `clear`) |
|
|
16
|
+
| `type` | `locator`, `value` (key by key) |
|
|
17
|
+
| `press` | `key`, optional `locator` |
|
|
18
|
+
| `select` | `locator`, `value` |
|
|
19
|
+
| `check` | `locator`, optional `checked` (false unchecks) |
|
|
20
|
+
| `scroll` | `deltaY`, `waitMs` |
|
|
21
|
+
| `goto` | `url`: absolute http(s), inside the recipe's domains (see below) |
|
|
22
|
+
| `extract` | optional `locator` (default: the page), `mode`: `text`, `html`, `links`, `attribute` (with `attribute`), `limit`, optional `as` (names the output) |
|
|
23
|
+
| `assert` | `locator`, `text`, or `urlIncludes` that must become true |
|
|
24
|
+
| `screenshot` | none |
|
|
25
|
+
|
|
26
|
+
`extract` and `assert` wait at most 5 s unless `timeoutMs` says otherwise, so a selector that is not there fails fast with `LOCATOR_NOT_FOUND`. Every other step waits 15 s. Read-only steps (`wait`, `goto`, `extract`, `assert`, `screenshot`) run without approval; anything else may ask the user.
|
|
27
|
+
|
|
28
|
+
```json call
|
|
29
|
+
{"action":"automation.run_recipe","args":{"url":"https://example.com/","schemaVersion":2,"steps":[{"type":"click","locator":{"role":"link","name":"More information"}},{"type":"assert","urlIncludes":"iana.org"},{"type":"extract","locator":{"css":"main"},"as":"body"}],"postconditions":[{"output":"body","nonEmpty":true}]}}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### When a locator is ambiguous
|
|
33
|
+
|
|
34
|
+
A v2 locator must match exactly one element. If it matches several, the step is **not executed** (no click, no typing; `effects` stays as it was) and the reply is `LOCATOR_AMBIGUOUS` with `error.candidates`: `total` and the first five matches (`role`, `name`, `text`, `visible`). Fix the locator, do not guess:
|
|
35
|
+
|
|
36
|
+
1. Make it specific: `exact: true`, a fuller `name`, a `label`, a `testId`, or a `framePath`.
|
|
37
|
+
2. Only if the elements really are interchangeable, pick one with `"index": 1` **and** an `"indexReason": "why this one"`. An `index` without a reason is rejected.
|
|
38
|
+
|
|
39
|
+
A step that finds no element at all (`LOCATOR_NOT_FOUND`) did nothing either: `executionStatus` is `failed` and `effects` does not become `unknown`. Only a step that found its element, started, and then timed out is `outcome_unknown`.
|
|
40
|
+
|
|
41
|
+
## Reading the result
|
|
42
|
+
|
|
43
|
+
A recipe never fails by throwing away what it did. `result` always holds:
|
|
44
|
+
|
|
45
|
+
| field | meaning |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `executionStatus` | `completed`; `failed` (a step failed or the deadline passed); `cancelled`; `outcome_unknown` (a step that changes the page was being performed when it timed out) |
|
|
48
|
+
| `validationStatus` | `passed`: there is at least one `assert` or postcondition and every one held. `failed`: one did not hold. `not_checked`: nothing verified the result |
|
|
49
|
+
| `completedSteps` | the steps that finished: `step`, `action`, `ok`, and for extract/screenshot `output`, the index into `outputs` (the value is stored once, there) |
|
|
50
|
+
| `failedStep` | `index` (1-based), `action`, `errorCode`, the original `message`, and `candidates` for `LOCATOR_AMBIGUOUS`; for a cancel or deadline, the step that did not start. A failed postcondition is reported as a step after the last one with `action: "postcondition"` |
|
|
51
|
+
| `effects` | `none`: nothing changed. `observed`: fill, type, clear, click, press, select or check steps completed. `unknown`: one of those failed in flight, so it may have happened |
|
|
52
|
+
| `outputs` | values from extract and screenshot steps, each with `step`, `action`, `value`, and `name` when the extract had `as`; an existing but empty element gives `""`, a missing element is `LOCATOR_NOT_FOUND` |
|
|
53
|
+
| `legacyFallback` | only on stored v1 assets: an unknown extract `mode` ran as `links` |
|
|
54
|
+
|
|
55
|
+
`ok` is true only when `executionStatus` is `completed` and `validationStatus` is not `failed`. Next steps:
|
|
56
|
+
|
|
57
|
+
- `OUTCOME_UNKNOWN`: do not run the recipe again. Read the page (`observe.read`) to see whether the click, fill or submit took effect, then continue from the real state.
|
|
58
|
+
- `VALIDATION_FAILED`: the page did not show the expected result. Read it, then correct the recipe, the assert or the postcondition. Steps before the failure already ran; do not repeat a submit.
|
|
59
|
+
- `failed` with `effects: observed`: the earlier steps are done. Resume from `failedStep.index`, not from the start.
|
|
60
|
+
- `failed` with `NOT_ACTIONABLE` and `effects: none`: the element was found but stayed disabled, hidden or moving until the timeout, so the step never ran. Wait for the page state it needs (`wait`), then retry.
|
|
61
|
+
- `cancelled`: the step that was running finished before the call returned; the page stays open and usable.
|
|
62
|
+
|
|
63
|
+
A test or run counts as passed only when it completed and nothing failed validation.
|
|
64
|
+
|
|
65
|
+
## Verifying the result: assert and postconditions
|
|
66
|
+
|
|
67
|
+
`assert` steps check in the middle of a recipe; **postconditions** are checked after all steps and belong to the asset. Use exactly one kind per entry:
|
|
68
|
+
|
|
69
|
+
- `{"selector":"#done"}`: a CSS selector that is visible. `{"text":"Saved"}`: text that is visible. `{"urlIncludes":"/inbox"}`: the final URL contains it. Each waits up to `timeoutMs` (default 5000).
|
|
70
|
+
- `{"output":"results","nonEmpty":true}`: the named extract output is not empty. `{"output":"results","allowEmpty":true}`: it only has to have been produced (an empty result is legitimate).
|
|
71
|
+
|
|
72
|
+
`validationStatus` is `passed` only when every `assert` and every postcondition holds. **A v2 draft with no `assert` and no postcondition can be saved, but its test cannot pass**: the reply is `ok: false` with `error.code: "VALIDATION_MISSING"` and `testStatus: "failed"`. Add a check that proves the business result (confirmation text, new URL, a non-empty output).
|
|
73
|
+
|
|
74
|
+
## Inputs and outputs
|
|
75
|
+
|
|
76
|
+
`{{name}}` in any string of a step or postcondition becomes an input. For v2, declare each one in `inputSchema` and the caller's values are checked and converted before the browser is touched:
|
|
77
|
+
|
|
78
|
+
```json call
|
|
79
|
+
{"action":"automation.develop","args":{"action":"save","kind":"recipe","schemaVersion":2,"name":"Catalog apply","domains":["example.com"],"recipe":[{"type":"fill","locator":{"label":"Limit"},"value":"{{limit}}"},{"type":"select","locator":{"label":"Sort"},"value":"{{sort}}"},{"type":"click","locator":{"role":"button","name":"Apply"}},{"type":"extract","locator":{"css":"#rows"},"as":"rows"}],"inputSchema":[{"name":"limit","type":"number","example":"3"},{"name":"sort","type":"enum","enumValues":["new","top"]}],"outputSchema":[{"name":"rows","type":"string"}],"postconditions":[{"text":"Sorted by {{sort}}, limit {{limit}}"},{"output":"rows","nonEmpty":true}]}}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `type`: `string`, `number` (a number or numeric text; stored canonically), or `enum` (must be one of `enumValues`). `required` defaults to true; an omitted optional input becomes an empty string. Wrong or missing inputs fail with `INVALID_ARGS` before anything runs.
|
|
83
|
+
- `outputSchema` names and types the outputs: each entry needs an `extract` with the same `as`. `number` parses the text (thousands commas allowed), `json` parses JSON (use it with `links`); a value that does not convert fails validation. `"dedupe":true` (string or json) removes repeated lines or elements, keeping the first of each, before the postconditions are checked.
|
|
84
|
+
- `requiredCapabilities` is recorded for later; it is not enforced.
|
|
85
|
+
- `automation.search` shows a v2 asset's `inputSchema`, so you can call `automation.run` without opening the recipe.
|
|
86
|
+
|
|
87
|
+
## goto
|
|
88
|
+
|
|
89
|
+
`goto` navigates the recipe's page. For a saved asset the URL must be inside its `domains` (the host or a subdomain), checked when saving and again when running, including where a redirect lands. For an inline recipe it may stay on the origin the recipe started on, or go to hosts listed in `allowedDomains`. A URL outside that fails with `POLICY_DENIED` before any step runs.
|
|
90
|
+
|
|
91
|
+
```json call
|
|
92
|
+
{"action":"automation.develop","args":{"action":"save","kind":"recipe","schemaVersion":2,"name":"Reset filter","domains":["example.com"],"recipe":[{"type":"goto","url":"https://example.com/search"},{"type":"clear","locator":{"label":"Filter"}},{"type":"assert","text":"All items"}]}}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Saving a draft
|
|
96
|
+
|
|
97
|
+
Save (`automation.develop`, `save`) replaces the whole draft, so send every field each time; `browser_index({action:"automation.develop.save"})` has its schema. Then `{"action":"validate","id":...}` and `{"action":"test","id":...,"url":...,"inputs":{...}}`. Test with realistic inputs, and when the recipe takes any, with `inputSets` (2 to 5 different inputs, each in a fresh context; `references/develop.md`), checking that the outputs differ. A draft explored with `browser_call` is built with `draft_from_journal` instead of by hand (`references/develop.md`). Activation is the user's decision. Do not put page content, cookies or passwords into an asset.
|
|
98
|
+
|
|
99
|
+
**Revisions.** Every save makes a new `revision` (with a `contentHash` over the steps and schema fields) and clears the test result. A test is recorded as a credential for the revision and content it ran on, with only a digest of the inputs; the test reply carries `revision` and `contentHash`. After any edit, test again. The user can activate only a revision whose latest test passed on exactly that content.
|
|
100
|
+
|
|
101
|
+
## Repairing an active asset
|
|
102
|
+
|
|
103
|
+
An active asset cannot be edited. When it stops working (the page changed), copy it, change the copy, test the copy:
|
|
104
|
+
|
|
105
|
+
```json call
|
|
106
|
+
{"action":"automation.develop","args":{"action":"fork","id":"active-asset-id"}}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The draft records `sourceAssetId` and `sourceRevision`. Failing tests of the draft never touch the active asset, which keeps running (`automation.run`). Once the draft passes with realistic inputs, tell the user it is ready; their activation archives the old asset and makes the draft the active one.
|
|
110
|
+
|
|
111
|
+
## Converting a v1 asset
|
|
112
|
+
|
|
113
|
+
```json call
|
|
114
|
+
{"action":"automation.develop","args":{"action":"convert","id":"v1-asset-id"}}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
creates a **new** v2 draft and leaves the original (even if active) exactly as it was; the draft records `sourceAssetId` and `sourceRevision`. Every CSS selector becomes `{"css": ...}` marked `explicitFirst`: it still takes the first match, as in v1, and is listed in `pendingDisambiguation`. To finish the migration: edit each listed step to a locator that matches one element (or an `index` with a reason), add an `assert` or a postcondition, save, and test. The `notes` in the reply list what else changed (shorter default timeouts, string inputs). An unknown `extract` mode or `wait` condition in the source refuses the conversion and says which step.
|
|
118
|
+
|
|
119
|
+
## v1 steps (existing assets)
|
|
120
|
+
|
|
121
|
+
A v1 step has a `type` and the fields it needs. Selectors are CSS and always use the first match.
|
|
122
|
+
|
|
123
|
+
| type | fields |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `wait` | `condition`: `selector` or `text` (with `value`), `load`, or `time` (with `waitMs`); optional `timeoutMs` |
|
|
126
|
+
| `click` | `selector` |
|
|
127
|
+
| `fill`, `type` | `selector`, `value` (`fill` replaces the value; `type` presses keys) |
|
|
128
|
+
| `press` | `key`, optional `selector` |
|
|
129
|
+
| `select` | `selector`, `value` |
|
|
130
|
+
| `check` | `selector`, optional `checked` (false unchecks) |
|
|
131
|
+
| `hover` | `selector` |
|
|
132
|
+
| `scroll` | `deltaY`, `waitMs` |
|
|
133
|
+
| `extract` | optional `selector`, `mode`: `text`, `html`, `links`, `attribute` (with `attribute`), `limit` |
|
|
134
|
+
| `assert` | `selector` or `text` that must become visible |
|
|
135
|
+
| `screenshot` | none |
|
|
136
|
+
|
|
137
|
+
```json call
|
|
138
|
+
{"action":"automation.run_recipe","args":{"url":"https://example.com/","steps":[{"type":"wait","condition":"selector","value":"main"},{"type":"extract","selector":"main","mode":"text","limit":20}]}}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
A v1 recipe with no `assert` still passes a test, but is stored as `legacy-unverified` (`evidenceLevel`): add an `assert` that proves the business result to get `verified`.
|
|
142
|
+
|
|
143
|
+
## Known limits
|
|
144
|
+
|
|
145
|
+
- A step that fails stops the recipe. Earlier steps have already run and are not rolled back, so do not retry a recipe that already submitted something without checking the page.
|
|
146
|
+
- A step that is already running cannot be interrupted. Cancel and deadline take effect between steps, so a long `wait` or a slow click delays the return by up to its own timeout.
|
|
147
|
+
- `extract.mode` must be `text`, `html`, `links` or `attribute`, and a `wait` condition one of those listed above; other values are rejected when a recipe is run inline or saved.
|
|
148
|
+
- A recipe passing a test with no `assert` (v1) means the steps ran, not that the business result was correct.
|
|
149
|
+
- An inline v2 recipe is not offered as a reusable candidate; save it as a draft if it should be reused.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Userscripts and built-in scripts
|
|
2
|
+
|
|
3
|
+
For extraction that is awkward through clicking, run JavaScript in a fresh page context.
|
|
4
|
+
|
|
5
|
+
## Built-in scripts first
|
|
6
|
+
|
|
7
|
+
They are trusted and read-only. `script.catalog` lists them (`article-clean`, `links`, `jsonld`, `forms`).
|
|
8
|
+
|
|
9
|
+
```json call
|
|
10
|
+
{"action":"script.run_builtin","args":{"url":"https://example.com/","scriptId":"links"}}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Your own userscript
|
|
14
|
+
|
|
15
|
+
Format: a Tampermonkey-style metadata block, then a function body that returns a JSON-serializable value. Only `@grant none` is supported (no `GM_*` APIs, no `@require`), and `@match` must cover the target URL. Declared inputs arrive in `__DSH_INPUTS__`.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
// ==UserScript==
|
|
19
|
+
// @name Read Heading
|
|
20
|
+
// @match https://example.com/*
|
|
21
|
+
// @grant none
|
|
22
|
+
// ==/UserScript==
|
|
23
|
+
return { heading: document.querySelector('h1')?.textContent || '', input: __DSH_INPUTS__.query || '' }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Validate before running; validation does not execute anything and reports the hash and capabilities.
|
|
27
|
+
|
|
28
|
+
```json call
|
|
29
|
+
{"action":"script.validate","args":{"source":"// ==UserScript==\n// @name Read Heading\n// @match https://example.com/*\n// @grant none\n// ==/UserScript==\nreturn document.title","url":"https://example.com/"}}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```json call
|
|
33
|
+
{"action":"script.run_userscript","args":{"url":"https://example.com/","source":"// ==UserScript==\n// @name Read Heading\n// @match https://example.com/*\n// @grant none\n// ==/UserScript==\nreturn document.title"}}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Running external code asks the user for approval unless the mode is `unrestricted`. If the page redirects outside the `@match`, the run is rejected.
|
|
37
|
+
|
|
38
|
+
## One-off expressions
|
|
39
|
+
|
|
40
|
+
`script.evaluate` runs a single JavaScript expression (up to 20,000 characters) on the current page with its origin and login state, so it can read cookies and storage that are not HttpOnly. Use it sparingly; it also asks for approval.
|
|
41
|
+
|
|
42
|
+
```json call
|
|
43
|
+
{"action":"script.evaluate","args":{"expression":"document.title"}}
|
|
44
|
+
```
|
package/cordis.patch.yml
CHANGED
|
@@ -3,11 +3,19 @@
|
|
|
3
3
|
# dsh plugin --profile <name> add @anweat/dsh-browser
|
|
4
4
|
# then boot the profile (the web profile has HMR disabled, so a restart applies it).
|
|
5
5
|
#
|
|
6
|
+
# Supports DSH 0.2.0-rc.2 only (see the peer ranges in package.json).
|
|
7
|
+
#
|
|
6
8
|
# Login state: prefer named `authProfiles` with explicit `allowedDomains` (see
|
|
7
9
|
# README). `storageStatePath` remains as a legacy global fallback.
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
10
|
+
#
|
|
11
|
+
# The Host's Settings service saves editable config as a separate top-level row of
|
|
12
|
+
# the profile's own patch (`- id: browser` with `config`), and a patch replaces its
|
|
13
|
+
# target row's whole `config`. So this insert row carries no config: the Config schema
|
|
14
|
+
# defaults supply the initial values and the profile row holds the saved settings.
|
|
15
|
+
#
|
|
16
|
+
# Model-facing text (tool descriptions, root guide/note, summaries, error hints, skill) can be
|
|
17
|
+
# overridden under `prompts` in that config row; the settings card's "Export default text" (or `pnpm prompts:dump` in a
|
|
18
|
+
# repository checkout) prints every default in the exact structure. See README "自定义提示文本".
|
|
11
19
|
- insert:
|
|
12
20
|
- id: browser
|
|
13
21
|
name: '@anweat/dsh-browser'
|