From 0be8212748138ed99c60da1a00b2377643fc8ab9 Mon Sep 17 00:00:00 2001 From: Yota Hamada Date: Wed, 23 Sep 2026 21:35:34 +0900 Subject: [PATCH 1/3] Document browser automation actions Add a Browser page for browser.extract and browser.run: operations, model configuration, variables and secrets, outputs, screenshots, the replay cache, profiles, and human input. List the actions in the sidebar and the YAML specification. Refs dagucloud/dagu#2842 --- .vitepress/config.js | 1 + step-types/browser.md | 249 ++++++++++++++++++++++++ writing-workflows/yaml-specification.md | 1 + 3 files changed, 251 insertions(+) create mode 100644 step-types/browser.md diff --git a/.vitepress/config.js b/.vitepress/config.js index fe1ef54..a5f175b 100644 --- a/.vitepress/config.js +++ b/.vitepress/config.js @@ -417,6 +417,7 @@ const fullSidebar = [ ], }, { text: "LLM", link: "/step-types/llm/" }, + { text: "Browser", link: "/step-types/browser" }, { text: "SQL", link: "/step-types/sql/", diff --git a/step-types/browser.md b/step-types/browser.md new file mode 100644 index 0000000..e369a68 --- /dev/null +++ b/step-types/browser.md @@ -0,0 +1,249 @@ +# Browser + +Automate websites that have no API. A browser step drives a local Chrome with natural-language instructions, extracts structured data into step outputs, and can pause for a person, for example to enter a one-time code, then continue in the same browser. + +Browser steps are powered by the [Stagehand](https://github.com/browserbase/stagehand) Go SDK. Every model request goes through Dagu's own [LLM providers](/step-types/llm/providers), so the step uses the same `llm` configuration as `chat.completion`, and API keys never enter the browser. + +## Requirements + +- Google Chrome or Chromium installed on the host that runs the step. Dagu uses `browser.executable`, then `CHROME_PATH`, then a standard install location. The Dagu container image does not include a browser. +- A model configured with a DAG-level `llm` block or `with.llm`. + +## Quick Start + +`browser.extract` opens a page and returns structured data: + +```yaml +secrets: + - name: ANTHROPIC_API_KEY + provider: env + key: ANTHROPIC_API_KEY + +llm: + provider: anthropic + model: claude-sonnet-5 + +steps: + - id: hn + action: browser.extract + with: + url: https://news.ycombinator.com + instruction: The top 5 stories with their points + schema: + type: object + properties: + stories: + type: array + items: + type: object + properties: + title: { type: string } + points: { type: integer } + + - id: report + depends: hn + run: echo '${steps.hn.outputs.stories}' +``` + +`browser.run` runs several operations in one browser session: + +```yaml +steps: + - id: invoice + action: browser.run + with: + url: https://portal.vendor.com/billing + browser: + profile: vendor + allowed_domains: [portal.vendor.com] + variables: + user: ${VENDOR_USER} + password: ${VENDOR_PASSWORD} + do: + - act: Sign in with %user% and %password% + when: A login form is visible + - expect: The billing page lists at least one invoice + - extract: + instruction: The most recent invoice + schema: + type: object + properties: + invoice_number: { type: string } + total: { type: number } + - act: Download the most recent invoice PDF + + - id: book + depends: invoice + run: ./book.sh "${steps.invoice.outputs.invoice_number}" +``` + +## Operations + +`with.do` lists operations that run in order. Each item sets exactly one operation: + +| Operation | Value | Effect | +|-----------|-------|--------| +| `goto` | URL | Navigate the current tab. | +| `act` | Instruction, or `{instruction, cache}` | Perform an action described in natural language: click, type, select, scroll, press a key. | +| `extract` | `{instruction, schema}` | Extract data from the page. The schema must be a JSON Schema with `type: object`. | +| `expect` | Statement | Fail the step unless the statement about the page is true. The failure shows the model's reason. | +| `wait` | `{selector}` or `{duration}` | Wait until a CSS selector is visible, or pause, such as `2s`. | +| `screenshot` | Name | Save a PNG of the page as a run artifact. | +| `ask` | `{prompt, as, timeout}` | Wait for a person's answer. See [Human Input](#human-input). | + +Any operation can also set: + +| Field | Description | +|-------|-------------| +| `when` | A statement about the page. The operation is skipped unless it is true. | +| `timeout` | Maximum time for the operation, such as `30s`. Defaults to `2m`. | + +`with.url` is opened before the first operation. + +## Model + +Browser steps use the DAG-level `llm` block. `with.llm` replaces it entirely for one step: + +```yaml +steps: + - id: prices + action: browser.extract + with: + llm: + provider: openrouter + model: deepseek/deepseek-v4-flash + url: https://shop.example.com + instruction: All product names and prices + schema: + type: object + properties: + products: { type: array } +``` + +A list of models under `model` is tried in order for each request. Every provider that works with `chat.completion` works here, including local models. Browser automation needs a model that follows tool-call schemas reliably; small local models often pick the wrong element. + +## Secrets and Variables + +Put secret values in `with.variables` and reference them as `%name%` in `act` instructions. The browser types the value; the model sees only the name. + +```yaml +secrets: + - name: PORTAL_PASSWORD + provider: env + key: PORTAL_PASSWORD + +steps: + - id: login + action: browser.run + with: + url: https://portal.example.com/login + variables: + password: ${PORTAL_PASSWORD} + do: + - act: Type %password% into the password field and sign in +``` + +Do not write `${PORTAL_PASSWORD}` inside an instruction. The reference would resolve to the secret, which would be sent to the model, so the step fails before starting a browser. Secret and variable values are also masked in text sent to the model, in the step log, and in the timeline. + +## Outputs + +The top-level properties each `extract` schema lists become step outputs, readable as `${steps..outputs.}` and checked when the DAG loads. Fields a schema does not list are dropped. Two extracts in one step cannot list the same property. + +When the step succeeds with outputs, stdout is one JSON object of them, so `output:` also works. Operation progress is written to the step's stderr log: + +```text +[start] goto "https://portal.vendor.com/billing" (completed, 0 tokens, 800ms) +[1/4] act "Sign in with %user% and %password%" → fill xpath=/html[1]/body[1]/form[1]/input[1] %user%; … (cache-hit, 0 tokens, 400ms) +[3/4] extract "The most recent invoice" → {"invoice_number":"INV-8812","total":412.5} (completed, 1204 tokens, 2.1s) +``` + +## Screenshots and Downloads + +Browser steps store files as [run artifacts](/writing-workflows/artifacts) under `browser//`: + +| `browser.screenshots` | Automatic screenshots | +|-----------------------|-----------------------| +| `on_failure` (default) | When the step fails, and at the end of a successful step. | +| `each` | After every operation, plus the above. | +| `never` | None. `screenshot` operations still save. | + +Files the page downloads are saved under `browser//downloads/`. + +A DAG with a browser step enables artifact storage automatically. With `artifacts.enabled: false`, no screenshots or downloads are saved and a `screenshot` operation fails. + +## Replay Cache + +A successful `act` records the actions it performed. The next run of the same step replays them without asking the model when the operation's position, its instruction, and the page URL (without query or fragment) match. Scheduled runs against a stable page therefore spend tokens only on `extract`, `expect`, and `when`. + +When a replay fails because the page changed, the step asks the model again, records the new actions, and marks the operation `healed` in the log. Disable the cache with `with.cache: false`, or for one operation with `act: {instruction: ..., cache: false}`. + +## Profiles + +`browser.profile` keeps cookies and storage across runs, so a site stays signed in: + +```yaml +with: + browser: + profile: vendor + do: + - act: Sign in with %user% and %password% + when: A login form is visible +``` + +Profiles are stored on the host that runs the step. Runs that use the same profile run one at a time. A run fails immediately when another run is waiting for input with the same profile open. + +## Human Input + +`ask` pauses the step until someone answers in the Web UI: + +```yaml +do: + - act: Sign in with %user% and %password% + - ask: + prompt: Enter the 6-digit code sent to your phone + as: otp + timeout: 15m + when: The page asks for a verification code + - act: Type %otp% into the code field and submit + when: The page asks for a verification code +``` + +The step enters **Waiting** with the question in the step's **Agent** tab. The browser stays open. Answering resumes the step in the same browser at the next operation, with the answer available as `%otp%`. Rejecting the question fails the step. The browser stays open for `timeout` (default `1h`); after that, the answer fails the step and **Start clean session** runs the step again from the beginning. + +Answers are stored in the run's history, like other human input. Use `ask` for short-lived codes, not long-term secrets. + +In distributed mode, the answer resumes the step on the worker that holds the browser. + +## Safety + +- `browser.allowed_domains` limits navigation to the listed domains and their subdomains. Dagu also rejects a `goto` or `url` outside the list before navigating. +- Page content can contain instructions aimed at the model. Limit domains and keep operations specific. +- The browser runs with the permissions of the Dagu process. + +## Web UI + +The step's **Agent** tab shows each operation with its status, token use, and screenshot thumbnails, and holds pending questions. + +## Browser Options + +| Field | Description | +|-------|-------------| +| `browser.headless` | Run without a window. Defaults to `true`. | +| `browser.executable` | Chrome or Chromium binary. | +| `browser.viewport` | `{width, height}` in pixels. | +| `browser.proxy` | Proxy server URL. Authenticated proxies are not supported. | +| `browser.allowed_domains` | Domains the browser may navigate to. | +| `browser.screenshots` | `on_failure`, `each`, or `never`. | +| `browser.profile` | Persistent profile name. | + +## Not Supported Yet + +- Attaching to an already running browser. +- Hosted browsers, CAPTCHA solving, and stealth fingerprints. +- Screenshot-based extraction. + +## Related + +- [LLM Providers](/step-types/llm/providers) +- [Artifacts](/writing-workflows/artifacts) +- [Human Tasks](/writing-workflows/human-tasks) diff --git a/writing-workflows/yaml-specification.md b/writing-workflows/yaml-specification.md index 4c4ec4d..6f18b1c 100644 --- a/writing-workflows/yaml-specification.md +++ b/writing-workflows/yaml-specification.md @@ -880,6 +880,7 @@ Accepted built-in action names: |--------|---------| | `artifact.list`, `artifact.read`, `artifact.write` | DAG-run artifact operations. | | `archive.create`, `archive.extract`, `archive.list` | Archive operations. | +| `browser.extract`, `browser.run` | Browser automation in a local Chrome. See [Browser](/step-types/browser). | | `chat.completion` | LLM chat completion. | | `container.run` | Container executor. | | `dag.run` | Run a child DAG synchronously. | From c0deed3d6f1fa60f19f255a8061c908dda89fc68 Mon Sep 17 00:00:00 2001 From: Yota Hamada Date: Wed, 23 Sep 2026 22:48:24 +0900 Subject: [PATCH 2/3] Correct browser docs after review Describe fixed expect and when checks, allowed_domains matching and its effect on every request, download waiting, the new screenshot default and unmasked artifacts, masking and %name% rules, replay cache limits, per-host profiles and cache in distributed mode, prompt injection, and ask on Windows. Examples declare their secrets. Refs dagucloud/dagu#2842 --- step-types/browser.md | 85 ++++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/step-types/browser.md b/step-types/browser.md index e369a68..ff76ea5 100644 --- a/step-types/browser.md +++ b/step-types/browser.md @@ -1,13 +1,13 @@ # Browser -Automate websites that have no API. A browser step drives a local Chrome with natural-language instructions, extracts structured data into step outputs, and can pause for a person, for example to enter a one-time code, then continue in the same browser. +Automate websites that have no API. A browser step drives a local Chrome with natural-language instructions, extracts structured data into step outputs, checks page state, and can pause for a person, for example to enter a one-time code, then continue in the same browser. Browser steps are powered by the [Stagehand](https://github.com/browserbase/stagehand) Go SDK. Every model request goes through Dagu's own [LLM providers](/step-types/llm/providers), so the step uses the same `llm` configuration as `chat.completion`, and API keys never enter the browser. ## Requirements - Google Chrome or Chromium installed on the host that runs the step. Dagu uses `browser.executable`, then `CHROME_PATH`, then a standard install location. The Dagu container image does not include a browser. -- A model configured with a DAG-level `llm` block or `with.llm`. +- A model configured with a DAG-level `llm` block or `with.llm`. Use a model that follows tool-call schemas reliably. ## Quick Start @@ -48,6 +48,14 @@ steps: `browser.run` runs several operations in one browser session: ```yaml +secrets: + - name: VENDOR_USER + provider: env + key: VENDOR_USER + - name: VENDOR_PASSWORD + provider: env + key: VENDOR_PASSWORD + steps: - id: invoice action: browser.run @@ -61,8 +69,8 @@ steps: password: ${VENDOR_PASSWORD} do: - act: Sign in with %user% and %password% - when: A login form is visible - - expect: The billing page lists at least one invoice + when: {selector: "form#login"} + - expect: {text: Invoices} - extract: instruction: The most recent invoice schema: @@ -86,7 +94,7 @@ steps: | `goto` | URL | Navigate the current tab. | | `act` | Instruction, or `{instruction, cache}` | Perform an action described in natural language: click, type, select, scroll, press a key. | | `extract` | `{instruction, schema}` | Extract data from the page. The schema must be a JSON Schema with `type: object`. | -| `expect` | Statement | Fail the step unless the statement about the page is true. The failure shows the model's reason. | +| `expect` | [Condition](#conditions) | Fail the step unless the condition holds. | | `wait` | `{selector}` or `{duration}` | Wait until a CSS selector is visible, or pause, such as `2s`. | | `screenshot` | Name | Save a PNG of the page as a run artifact. | | `ask` | `{prompt, as, timeout}` | Wait for a person's answer. See [Human Input](#human-input). | @@ -95,11 +103,24 @@ Any operation can also set: | Field | Description | |-------|-------------| -| `when` | A statement about the page. The operation is skipped unless it is true. | +| `when` | A [condition](#conditions) checked once before the operation. The operation is skipped unless it holds. | | `timeout` | Maximum time for the operation, such as `30s`. Defaults to `2m`. | `with.url` is opened before the first operation. +## Conditions + +`expect` and `when` take either kind of condition: + +| Form | Example | Checked by | +|------|---------|------------| +| Statement | `expect: The cart shows two items` | The model. Results can vary between runs. | +| `text` | `expect: {text: Order confirmed}` | The page's visible text contains it. | +| `selector` | `when: {selector: "#cookie-banner"}` | A CSS selector matches a visible element. | +| `url` | `expect: {url: /billing}` | The current URL contains it. | + +Fixed checks (`text`, `selector`, `url`) make no model call and give the same answer on every run, so prefer them for monitoring. A fixed `expect` is retried until the operation timeout, because the page may still be updating. + ## Model Browser steps use the DAG-level `llm` block. `with.llm` replaces it entirely for one step: @@ -120,11 +141,11 @@ steps: products: { type: array } ``` -A list of models under `model` is tried in order for each request. Every provider that works with `chat.completion` works here, including local models. Browser automation needs a model that follows tool-call schemas reliably; small local models often pick the wrong element. +A list of models under `model` is tried in order for each request. Every request asks the model for a tool call whose parameters are the expected JSON. A provider or model without tool calling works only if it replies with plain JSON text, so choose a model that follows tool-call schemas reliably. Small local models often pick the wrong element. ## Secrets and Variables -Put secret values in `with.variables` and reference them as `%name%` in `act` instructions. The browser types the value; the model sees only the name. +Declare secrets under `secrets:`, put them in `with.variables`, and reference them as `%name%` in `act` instructions. The browser types the value; the model sees only the name. ```yaml secrets: @@ -143,7 +164,9 @@ steps: - act: Type %password% into the password field and sign in ``` -Do not write `${PORTAL_PASSWORD}` inside an instruction. The reference would resolve to the secret, which would be sent to the model, so the step fails before starting a browser. Secret and variable values are also masked in text sent to the model, in the step log, and in the timeline. +- Do not write `${PORTAL_PASSWORD}` inside an instruction. The step fails before starting a browser when a model-bound text contains the value of a declared secret of four or more characters. Values that only come from `env:` are not treated as secrets. +- A `%name%` must be a `with.variables` key or the `as` of an earlier `ask`; anything else fails validation. +- Declared secrets and `ask` answers of four or more characters are masked in text sent to the model, in the step log, and in the timeline. Plain variables are not masked. ## Outputs @@ -155,27 +178,31 @@ When the step succeeds with outputs, stdout is one JSON object of them, so `outp [start] goto "https://portal.vendor.com/billing" (completed, 0 tokens, 800ms) [1/4] act "Sign in with %user% and %password%" → fill xpath=/html[1]/body[1]/form[1]/input[1] %user%; … (cache-hit, 0 tokens, 400ms) [3/4] extract "The most recent invoice" → {"invoice_number":"INV-8812","total":412.5} (completed, 1204 tokens, 2.1s) +[4/4] download "invoice-8812.pdf" → browser/invoice/downloads/invoice-8812.pdf (completed, 0 tokens, 0s) ``` ## Screenshots and Downloads -Browser steps store files as [run artifacts](/writing-workflows/artifacts) under `browser//`: +Browser steps store files as [run artifacts](/writing-workflows/artifacts) under `browser//`. A DAG with a browser step enables artifact storage automatically. These files are not masked and can show signed-in pages with personal data; use `screenshots: never` or `artifacts.enabled: false` to keep them out of run history. | `browser.screenshots` | Automatic screenshots | |-----------------------|-----------------------| -| `on_failure` (default) | When the step fails, and at the end of a successful step. | -| `each` | After every operation, plus the above. | +| `on_failure` (default) | When the step fails. | +| `final` | When the step fails, and at the end of a successful step. | +| `each` | After every operation, plus the `final` ones. | | `never` | None. `screenshot` operations still save. | -Files the page downloads are saved under `browser//downloads/`. +Files the page downloads are saved under `browser//downloads/` with the name the site suggests. After each operation the step waits for running downloads, up to that operation's timeout, and before it ends it waits a few seconds for a late download to begin. A canceled download, or one that does not finish in time, fails the step. Downloaded files appear in the timeline. -A DAG with a browser step enables artifact storage automatically. With `artifacts.enabled: false`, no screenshots or downloads are saved and a `screenshot` operation fails. +With `artifacts.enabled: false`, no screenshots are saved, a `screenshot` operation fails, and the browser refuses downloads. ## Replay Cache -A successful `act` records the actions it performed. The next run of the same step replays them without asking the model when the operation's position, its instruction, and the page URL (without query or fragment) match. Scheduled runs against a stable page therefore spend tokens only on `extract`, `expect`, and `when`. +A successful `act` records the actions it performed. The next run of the same step on the same host replays them without asking the model when the operation's position, its instruction, and the page URL (without query or fragment) match. When a replay fails because the page changed, the step asks the model again, records the new actions, and marks the operation `healed` in the log. -When a replay fails because the page changed, the step asks the model again, records the new actions, and marks the operation `healed` in the log. Disable the cache with `with.cache: false`, or for one operation with `act: {instruction: ..., cache: false}`. +- The cache covers `act` only. `extract` and statement conditions call the model on every run. With fixed conditions, a rerun calls the model only for `extract`. +- A replay clicks the recorded element location. After a layout change it can hit a different element without failing, so follow important acts with an `expect`, preferably a fixed one. +- Disable the cache with `with.cache: false`, or for one operation with `act: {instruction: ..., cache: false}`. ## Profiles @@ -187,7 +214,7 @@ with: profile: vendor do: - act: Sign in with %user% and %password% - when: A login form is visible + when: {selector: "form#login"} ``` Profiles are stored on the host that runs the step. Runs that use the same profile run one at a time. A run fails immediately when another run is waiting for input with the same profile open. @@ -203,26 +230,31 @@ do: prompt: Enter the 6-digit code sent to your phone as: otp timeout: 15m - when: The page asks for a verification code + when: {text: Verification code} - act: Type %otp% into the code field and submit - when: The page asks for a verification code + when: {text: Verification code} ``` -The step enters **Waiting** with the question in the step's **Agent** tab. The browser stays open. Answering resumes the step in the same browser at the next operation, with the answer available as `%otp%`. Rejecting the question fails the step. The browser stays open for `timeout` (default `1h`); after that, the answer fails the step and **Start clean session** runs the step again from the beginning. +The step enters **Waiting** with the question in the step's **Agent** tab. The browser stays open. Answering resumes the step in the same browser at the next operation, with the answer available as `%otp%`. If an act needs an answer whose `ask` was skipped, the step fails instead of typing `%otp%`. Rejecting the question fails the step. The browser stays open for `timeout` (default `1h`); after that, the answer fails the step and **Start clean session** runs the step again from the beginning. Answers are stored in the run's history, like other human input. Use `ask` for short-lived codes, not long-term secrets. -In distributed mode, the answer resumes the step on the worker that holds the browser. +`ask` is not supported on Windows, where the browser cannot outlive the step process; such a step fails at the start. ## Safety -- `browser.allowed_domains` limits navigation to the listed domains and their subdomains. Dagu also rejects a `goto` or `url` outside the list before navigating. -- Page content can contain instructions aimed at the model. Limit domains and keep operations specific. +- Page content is untrusted and is sent to the model. A hostile page, or content other users posted on an allowed site, can try to steer an `act`, for example into typing `%password%` into the wrong field. Use variables only on pages you trust, keep instructions specific, and follow sensitive acts with an `expect`. +- `browser.allowed_domains` limits every request the page makes, including scripts, images, and API calls, so list the hosts a site loads resources from. `example.com` matches only that host; `*.example.com` matches its subdomains but not `example.com`. +- Dagu rejects a `goto` or `url` outside the list, and after every operation fails the step if a redirect or a click left the allowed domains. - The browser runs with the permissions of the Dagu process. +## Distributed Mode + +Browser steps run on the worker that picks them up, which needs Chrome. Profiles and the replay cache are stored on that worker, so pin steps that rely on them with `worker_selector`. An answered `ask` resumes on the worker that holds the browser. + ## Web UI -The step's **Agent** tab shows each operation with its status, token use, and screenshot thumbnails, and holds pending questions. +The step's **Agent** tab shows each operation with its status, token use, screenshot thumbnails, and downloads, and holds pending questions. ## Browser Options @@ -232,8 +264,8 @@ The step's **Agent** tab shows each operation with its status, token use, and sc | `browser.executable` | Chrome or Chromium binary. | | `browser.viewport` | `{width, height}` in pixels. | | `browser.proxy` | Proxy server URL. Authenticated proxies are not supported. | -| `browser.allowed_domains` | Domains the browser may navigate to. | -| `browser.screenshots` | `on_failure`, `each`, or `never`. | +| `browser.allowed_domains` | Hosts the page may reach. | +| `browser.screenshots` | `on_failure`, `final`, `each`, or `never`. | | `browser.profile` | Persistent profile name. | ## Not Supported Yet @@ -241,6 +273,7 @@ The step's **Agent** tab shows each operation with its status, token use, and sc - Attaching to an already running browser. - Hosted browsers, CAPTCHA solving, and stealth fingerprints. - Screenshot-based extraction. +- `ask` on Windows. ## Related From 60096641d63cb9a8850f772ae35cd56b7912ecbe Mon Sep 17 00:00:00 2001 From: Yota Hamada Date: Wed, 23 Sep 2026 23:05:24 +0900 Subject: [PATCH 3/3] Clarify browser domain, download, and when docs allowed_domains is enforced by the browser runtime for HTTP(S) requests only, and Dagu checks only the page URL. Document the within window for fixed when checks and how long downloads are waited for, and use a wildcard in the example. Refs dagucloud/dagu#2842 --- step-types/browser.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/step-types/browser.md b/step-types/browser.md index ff76ea5..930a10d 100644 --- a/step-types/browser.md +++ b/step-types/browser.md @@ -63,7 +63,8 @@ steps: url: https://portal.vendor.com/billing browser: profile: vendor - allowed_domains: [portal.vendor.com] + # Every host the site loads from, including CDNs and sign-in pages. + allowed_domains: ["*.vendor.com"] variables: user: ${VENDOR_USER} password: ${VENDOR_PASSWORD} @@ -119,7 +120,16 @@ Any operation can also set: | `selector` | `when: {selector: "#cookie-banner"}` | A CSS selector matches a visible element. | | `url` | `expect: {url: /billing}` | The current URL contains it. | -Fixed checks (`text`, `selector`, `url`) make no model call and give the same answer on every run, so prefer them for monitoring. A fixed `expect` is retried until the operation timeout, because the page may still be updating. +Fixed checks (`text`, `selector`, `url`) make no model call and give the same answer on every run, so prefer them for monitoring. A `selector` check holds when any matching element is visible. + +A fixed `when` reads the page once, right after the previous operation. When the page may still be loading, add `within` so the check keeps reading until the condition holds or the window ends: + +```yaml +- ask: {prompt: Enter the code, as: otp} + when: {text: Verification code, within: 10s} +``` + +A fixed `expect` keeps reading until `within`, or the operation timeout when `within` is not set. ## Model @@ -192,7 +202,7 @@ Browser steps store files as [run artifacts](/writing-workflows/artifacts) under | `each` | After every operation, plus the `final` ones. | | `never` | None. `screenshot` operations still save. | -Files the page downloads are saved under `browser//downloads/` with the name the site suggests. After each operation the step waits for running downloads, up to that operation's timeout, and before it ends it waits a few seconds for a late download to begin. A canceled download, or one that does not finish in time, fails the step. Downloaded files appear in the timeline. +Files the page downloads are saved under `browser//downloads/` with the name the site suggests. Only `act` and `goto` start downloads. Once one has run, the step waits for running downloads after every operation, and before it ends or pauses for an `ask` it waits a few seconds for a late download to begin. A download may run for the longest timeout of the acts and gotos run so far; give a large export's act a long `timeout`. A canceled download, or one still running at that timeout, fails the step. Downloaded files appear in the timeline. With `artifacts.enabled: false`, no screenshots are saved, a `screenshot` operation fails, and the browser refuses downloads. @@ -230,7 +240,7 @@ do: prompt: Enter the 6-digit code sent to your phone as: otp timeout: 15m - when: {text: Verification code} + when: {text: Verification code, within: 10s} - act: Type %otp% into the code field and submit when: {text: Verification code} ``` @@ -244,8 +254,8 @@ Answers are stored in the run's history, like other human input. Use `ask` for s ## Safety - Page content is untrusted and is sent to the model. A hostile page, or content other users posted on an allowed site, can try to steer an `act`, for example into typing `%password%` into the wrong field. Use variables only on pages you trust, keep instructions specific, and follow sensitive acts with an `expect`. -- `browser.allowed_domains` limits every request the page makes, including scripts, images, and API calls, so list the hosts a site loads resources from. `example.com` matches only that host; `*.example.com` matches its subdomains but not `example.com`. -- Dagu rejects a `goto` or `url` outside the list, and after every operation fails the step if a redirect or a click left the allowed domains. +- The browser runtime applies `browser.allowed_domains` to the page's HTTP(S) requests, including scripts, images, and API calls, so list the CDN and sign-in hosts a site loads from. WebSocket connections are not covered, and the runtime's check has a known bypass. `example.com` matches only that host; `*.example.com` matches its subdomains but not `example.com`. +- Dagu itself checks only the page URL: it rejects a `goto` or `url` outside the list, and after every operation fails the step if a redirect or a click left the allowed domains. - The browser runs with the permissions of the Dagu process. ## Distributed Mode @@ -264,7 +274,7 @@ The step's **Agent** tab shows each operation with its status, token use, screen | `browser.executable` | Chrome or Chromium binary. | | `browser.viewport` | `{width, height}` in pixels. | | `browser.proxy` | Proxy server URL. Authenticated proxies are not supported. | -| `browser.allowed_domains` | Hosts the page may reach. | +| `browser.allowed_domains` | Hosts the page's HTTP(S) requests may reach. | | `browser.screenshots` | `on_failure`, `final`, `each`, or `never`. | | `browser.profile` | Persistent profile name. |