Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
487a92a
docs(notifications): specify the first notification channels, publicU…
arg1998 Sep 29, 2026
96ce263
feat(contracts): channel API, publicUrl and the platform table
arg1998 Sep 29, 2026
91958ba
feat(core): renderer, screenshot store, Telegram setup and URL probe …
arg1998 Sep 29, 2026
174e490
feat(notifications): startup channel flag, screenshots and per-channe…
arg1998 Sep 29, 2026
ea07953
feat(notifications): sample notifications for previews and tests
arg1998 Sep 29, 2026
13675c8
feat(notifications): Telegram, Discord webhook, ntfy and webhook adap…
arg1998 Sep 29, 2026
bf518fb
feat(notifications): channel service, routes, publicUrl check and cha…
arg1998 Sep 29, 2026
58ab2ab
Merge branch 'n1/adapters' into feat/notifications-n1-channels
arg1998 Sep 29, 2026
6814b65
test(notifications): platform fakes, renderer goldens and adapter suites
arg1998 Sep 29, 2026
e09e145
ci(notifications): real ntfy job and the weekly live notification check
arg1998 Sep 29, 2026
d749058
feat(notifications): wire the platform adapters, screenshots and publ…
arg1998 Sep 29, 2026
f5a3ca2
Merge branch 'n1/adapters' into feat/notifications-n1-channels
arg1998 Sep 29, 2026
721db16
fix(notifications): notification channel routes name secret variables…
arg1998 Sep 29, 2026
0663ae2
feat(dashboard): notification channels, setup wizard, platform previe…
arg1998 Sep 29, 2026
f71b693
Merge branch 'feat/notifications-n1-channels' into n1/dashboard
arg1998 Sep 29, 2026
80dff4d
test(notifications): channel service, publicUrl check, image rule, tr…
arg1998 Sep 29, 2026
71a3a19
docs(notifications): channel setup walkthroughs, public address, scre…
arg1998 Sep 29, 2026
fa5289e
test(cli): startup channel flag planning and boot projection with the…
arg1998 Sep 29, 2026
b367b65
fix(notifications): Telegram links go into the text when the host wou…
arg1998 Sep 29, 2026
f03485e
ci(notifications): wait for ntfy.sh's cache in the live check
arg1998 Sep 29, 2026
f020dcd
feat(dashboard): channel page tests, captured preview fixtures and re…
arg1998 Sep 29, 2026
0c7c92b
Merge branch 'feat/notifications-n1-channels' into n1/dashboard
arg1998 Sep 29, 2026
d6f0a4e
docs(notifications): take the final notifications guide over the anch…
arg1998 Sep 29, 2026
c607118
test(dashboard): end-to-end channels journey with a webhook receiver …
arg1998 Sep 29, 2026
e54751f
fix(dashboard): channel name suggestion without the wall clock; modul…
arg1998 Sep 29, 2026
5e768e3
fix(dashboard): one clear publicUrl note in the channel preview
arg1998 Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/notification-channels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"browserhive": minor
---

Notifications on your phone: Telegram, Discord, ntfy and webhook channels, with screenshots, live updates and messages that delete themselves.

- **Get a message when an agent needs you.** Add a channel under **Notifications → Channels**: a Telegram bot (one-tap connect, no chat id to look up), a Discord webhook, an ntfy topic (scan a QR code with the ntfy app) or a webhook of your own. A wizard shows the exact line to set the token for how you run BrowserHive, checks that it is set, previews the message exactly as it will look, and sends a test. Presets pick what to send (*Needs me now*, *Problems*, *Wrap-ups*); **Advanced** adds minimum severity, session patterns, harness, quiet hours with a time zone and the content level.
- **Messages keep up.** When you resolve an attention request, the chat message is edited in place, silently, and its buttons disappear; a growing group of tool errors updates its count. Every send, edit and delete is in the new **Delivery log**, live, with a sentence for anything that was not sent ("quiet hours", "the platform refused the token").
- **Screenshots, when you want them.** Off by default, per channel and category: the page when an agent asked for help (CAPTCHAs included), the login page before a vault fill (never during one), a crashed session's last frame. Form fields can be masked.
- **Self-destruct.** Delete messages after a time you choose per category, or once they are resolved. Telegram only allows 48 hours, so its timers stop at 47.
- **Links that open on your phone.** The new `publicUrl` key (`--publicUrl`, `BROWSERHIVE_PUBLIC_URL`) is the address where you reach the dashboard (a Tailscale name, your reverse proxy, a Cloudflare tunnel). Notification links use it, its host is trusted without `allowedHosts`, and the CSRF check accepts it even when your proxy rewrites `Host`. The System page and `browserhive doctor` check that it really reaches this BrowserHive.
- **Your accounts, your tokens.** BrowserHive runs no servers or shared bots. Tokens stay in environment variables; channels store only the variable names, so a database backup never contains one.
- **For servers and containers**, declare channels at startup with `--notificationChannel "telegram:name=phone,token=env:BH_TG_TOKEN,chat=123456"` (repeatable; a token typed into the flag is refused). They show in the dashboard with a "from startup" badge.
- **From a terminal:** `browserhive channels list`, `channels test <name>` and `channels preview <name>`; `browserhive doctor` checks every channel's variables and `publicUrl`.
- New REST endpoints under `/api/v1/channels` (scopes `channels:read`, `channels:write`), `GET /api/v1/system/public-url`, a `channels` WebSocket topic, and `instance_id` in `GET /health`.
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,43 @@ jobs:
- if: env.RUN == 'true'
run: bun run test:integration

ntfy:
# The ntfy adapter against a real ntfy server (binwiederhier/ntfy): publish, read back, upload
# a screenshot, replace by sequence id, delete. Needs no secrets. Not a required check; the
# adapter is also covered by the fakes in `unit`.
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: .bun-version
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.bun/install/cache
key: 'bun-${{ runner.os }}-${{ hashFiles(''bun.lock'') }}'
- run: bun install --frozen-lockfile
# A service container cannot pass the image's `serve` command, so the server is started here.
- name: Start ntfy
run: |
docker run -d --name ntfy -p 127.0.0.1:8080:80 \
-e NTFY_BASE_URL=http://127.0.0.1:8080 \
-e NTFY_CACHE_FILE=/tmp/cache.db \
-e NTFY_ATTACHMENT_CACHE_DIR=/tmp/attachments \
binwiederhier/ntfy:v2.28.0 serve
for _ in $(seq 1 30); do
if curl -sf http://127.0.0.1:8080/v1/health > /dev/null; then break; fi
sleep 1
done
curl -sf http://127.0.0.1:8080/v1/health
- name: ntfy adapter against the real server
env:
BHDEV_NTFY_URL: http://127.0.0.1:8080
run: bun test packages/core/test/integration/notifications/ntfy-live.test.ts
- if: failure()
run: docker logs ntfy || true

sandbox-matrix:
# Cross-OS sandbox and stealth smoke check: launches sessions through BrowserHive under each
# `sandbox` setting (off, auto, on) and with an agent's `chromiumSandbox: true`, for the bundled
Expand Down
69 changes: 69 additions & 0 deletions .github/workflows/notify-live.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: notify-live

# Live notification check (spec 09 §8): real Telegram, a Discord webhook and ntfy, through the
# real adapters: send with a screenshot, read back where the platform allows, edit, delete. It
# guards against the fakes drifting from the platforms. Runs weekly, on dispatch, and on pull
# requests labelled `live-notify` from this repository (never from forks). The `notify-live`
# environment holds the secrets and needs the owner's approval. A platform whose secrets are not
# set is skipped with a note. A failure opens (or comments on) an issue.

on:
schedule:
- cron: '0 8 * * 1'
workflow_dispatch:
pull_request:
types: [labeled, opened, reopened, synchronize]

permissions:
contents: read

concurrency:
group: notify-live-${{ github.ref }}
cancel-in-progress: true

jobs:
live:
if: >-
github.event_name != 'pull_request' ||
(contains(github.event.pull_request.labels.*.name, 'live-notify') &&
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
environment: notify-live
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version-file: .bun-version
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.bun/install/cache
key: 'bun-${{ runner.os }}-${{ hashFiles(''bun.lock'') }}'
- run: bun install --frozen-lockfile
- name: Send, read back, edit and delete on each platform
env:
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
NTFY_TOPIC: ${{ secrets.NTFY_TOPIC }}
run: bun scripts/notify-live.ts
- name: Open or update the drift issue
if: failure() && github.event_name != 'pull_request'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
title="Weekly live notification check failed"
body="The live notification check against the real platforms failed.

- Run: $RUN_URL

A platform's API may have changed, or a secret in the notify-live environment expired. The fakes in packages/core/test/helpers/fake-platforms.ts may have drifted from the real platforms; see the job summary for which platform failed."
existing="$(gh issue list --state open --search "\"$title\" in:title" --json number --jq '.[0].number')"
if [ -n "$existing" ]; then
gh issue comment "$existing" --body "$body"
else
gh issue create --title "$title" --body "$body"
fi
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ BrowserHive is a local MCP server that gives AI agents isolated, stealthy Chromi
- [Security model](guide/security.md): authentication, bind rules, the vault model, redaction, what is recorded
- [Vault](guide/vault.md): Bitwarden setup, folder policies, bindings, confirmations
- [Human takeover](guide/attention.md): `request_attention` and the live view
- [Notifications](guide/notifications.md): what is notified, how a notification changes over its life, and how delivery to chat apps works
- [Notifications](guide/notifications.md): notifications on your phone through Telegram, Discord, ntfy or a webhook, with screenshots, self-destruct and a public address for links
- [Stealth](guide/stealth.md): what it does, what it does not, ceilings, proxies
- [Telemetry](guide/telemetry.md): OpenTelemetry export and a local Grafana stack
- [Command line](guide/cli.md): every command and exit code
Expand Down
32 changes: 31 additions & 1 deletion docs/guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ browserhive config show|schema|validate
browserhive db status|backup|restore <file>|migrate
browserhive admin reset-password
browserhive admin tokens list|create <name>|revoke <name>
browserhive channels list | test <name> | preview <name> [--sample <kind>]
browserhive version | --version | -v
browserhive help [command] | --help | -h
```
Expand Down Expand Up @@ -42,6 +43,25 @@ Output streams: under `--transport stdio`, stdout carries only MCP frames and ev

All server flags are in the [configuration reference](../reference/configuration.md).

### Notification channels

`--notificationChannel` declares a notification channel for this run ([startup channels](notifications.md#startup-channels)). Repeat it for several channels. It is a flag only: there is no environment variable or config-file key for it.

```
--notificationChannel "<platform>:<param>=<value>,<param>=<value>…"
```

A list value joins its items with `+`; a value cannot contain `,` (write `%2C`). Secret parameters are always `env:NAME`, the name of an environment variable (not starting with `BROWSERHIVE_`); a secret written into the flag is refused with exit 64, because process arguments are visible to other users of the machine.

| Platform | Parameters |
|---|---|
| `telegram` | `name`, `token=env:NAME`, `chat` (a chat id), optional `thread` (a forum topic id) |
| `discord` | `name`, `webhook=env:NAME` (the webhook URL), optional `mode=webhook` |
| `ntfy` | `name`, `topic` (a topic, or `env:NAME`), optional `server` (default `https://ntfy.sh`), `token=env:NAME` |
| `webhook` | `name`, `url` (a URL, or `env:NAME`), optional `secret=env:NAME` (the signing key) |

Rules, all optional: `categories` (`needs-you+problems+wrap-ups+reports+system`), `min` (`info`, `warn`, `error`, `critical`), `sessions` (session name patterns such as `shop-*`), `harness`, `content` (`counts`, `titles`, `full`), `quiet=22:00-07:00` with `tz=Europe/Berlin`, `ttl.<category>=2h` (Telegram at most `47h`), `deleteWhenResolved` (`true`, or a `+` list of categories), `images` (a `+` list of categories; needs `content=full`) and `maskImages=true`.

## `init`

One-time setup, safe to re-run:
Expand All @@ -68,7 +88,7 @@ One-time setup, safe to re-run:

## `doctor`

Checks Bun, browsers (the bundled Chromium, installed Chrome and Edge, that the configured `defaultChannel` is installed, version drift, managed policies that block automation), Chromium's sandbox for each installed browser (each is launched once), running as root or in a container, data directory permissions and disk space, configuration, port availability, `bw` when the vault is on, the database and pending migrations, the OTLP endpoint when telemetry is on, `maxSessions` against RAM, and config-file permissions when it holds secrets (not when `authTokens` only [references](configuration.md#references) environment variables). It counts the values that came from references and warns about each referenced variable that was not set, so its default is in use. It also warns when the data directory contains an unrecognised data file, which BrowserHive neither reads nor migrates.
Checks Bun, browsers (the bundled Chromium, installed Chrome and Edge, that the configured `defaultChannel` is installed, version drift, managed policies that block automation), Chromium's sandbox for each installed browser (each is launched once), running as root or in a container, data directory permissions and disk space, configuration, port availability, `bw` when the vault is on, the database and pending migrations, the OTLP endpoint when telemetry is on, `maxSessions` against RAM, `publicUrl` (whether it reaches this BrowserHive; see [public address](notifications.md#public-address)), notification channels (each `--notificationChannel` parses and every variable a channel names is set), and config-file permissions when it holds secrets (not when `authTokens` only [references](configuration.md#references) environment variables). It counts the values that came from references and warns about each referenced variable that was not set, so its default is in use. It also warns when the data directory contains an unrecognised data file, which BrowserHive neither reads nor migrates.

`--json` prints `[{ check, status, detail }]`. Exit codes: `0` all good, `2` warnings only, `1` a check failed. When the configured browser cannot run sandboxed, the text output ends with what to do on this machine. `--printApparmorProfile` prints an AppArmor profile for the configured browser (for Ubuntu 23.10+) and exits; install it yourself with `sudo tee /etc/apparmor.d/<name>` and `sudo apparmor_parser -r`.

Expand Down Expand Up @@ -115,6 +135,16 @@ See [Upgrading](upgrading.md).

Token commands work on the database directly when the server is stopped, or through the REST API of a running server with `--url <server>` and an operator credential.

## `channels`

| Command | Meaning |
|---|---|
| `channels list [--json]` | Every notification channel: platform, status (and "from startup"), where it sends, whether its variables are set, the last delivery and the last 24 hours. |
| `channels test <name> [--json]` | Sends a real test message. Exit `0` when the platform accepted it, `1` with the reason when it did not. |
| `channels preview <name> [--sample <kind>] [--json]` | Prints the platform request a send would make (secrets shown as variable names); sends nothing. Samples: `attention` (default), `attention-resolved`, `vault-confirm`, `tool-errors`, `crash`, `degraded`, `test`. |

They talk to the running server: `--url` defaults to the configured host and port, and `--token` (an operator API token) or `--cookie` authenticates. See [Notifications](notifications.md).

## `version`

```
Expand Down
4 changes: 3 additions & 1 deletion docs/guide/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,10 @@ A live tail of the server log with level, module, session and trace filters, pau

## System

Version, transport, uptime, bind address, sessions live versus the cap, open attention requests, live views, dashboard connections, default persistence, whether `evaluate` is allowed, vault backend, blocklist, retention, database size and schema version, telemetry endpoint and data directory. A stealth section shows the stealth level, driver, fingerprint and humanize defaults and CAPTCHA mode. **Browsers and sandbox** lists the browsers found on the machine (the bundled Chromium, an installed Google Chrome or Microsoft Edge) with version and path, marks the default one, and shows for each whether it runs inside Chromium's sandbox, with Chrome's own reason when it cannot. **MCP connections** lists the agents connected over MCP, live ones first, then recent ones; select one to see how its harness was recognised, its model and workspace, client and protocol, User-Agent, IP, any conflicting signals and extra labels. A session's Details tab shows its browser version and whether that session ran sandboxed, for live and closed sessions alike. Banners warn when `evaluate` is enabled together with the vault, when disk space is low, or when a subsystem is degraded. The configuration table lists every effective setting with its source (`cli`, `file`, `env`, `default`, `derived`) and the values it shadowed; secrets are shown as redacted. A value the config file reads from an environment variable has a `$NAME` chip under its source (outlined when the variable was not set and the default is used); select it to see the value as written in the file, or, for a secret, just the variable. **Only values from references** narrows the table to those keys, and the filter also matches variable names. See [References](configuration.md#references).
Version, transport, uptime, bind address, sessions live versus the cap, open attention requests, live views, dashboard connections, default persistence, whether `evaluate` is allowed, vault backend, blocklist, retention, database size and schema version, telemetry endpoint and data directory. A stealth section shows the stealth level, driver, fingerprint and humanize defaults and CAPTCHA mode. **Browsers and sandbox** lists the browsers found on the machine (the bundled Chromium, an installed Google Chrome or Microsoft Edge) with version and path, marks the default one, and shows for each whether it runs inside Chromium's sandbox, with Chrome's own reason when it cannot. **MCP connections** lists the agents connected over MCP, live ones first, then recent ones; select one to see how its harness was recognised, its model and workspace, client and protocol, User-Agent, IP, any conflicting signals and extra labels. **Public address** shows `publicUrl` and whether it reaches this BrowserHive (see [public address](notifications.md#public-address)), with **Check again**. A session's Details tab shows its browser version and whether that session ran sandboxed, for live and closed sessions alike. Banners warn when `evaluate` is enabled together with the vault, when disk space is low, or when a subsystem is degraded. The configuration table lists every effective setting with its source (`cli`, `file`, `env`, `default`, `derived`) and the values it shadowed; secrets are shown as redacted. A value the config file reads from an environment variable has a `$NAME` chip under its source (outlined when the variable was not set and the default is used); select it to see the value as written in the file, or, for a secret, just the variable. **Only values from references** narrows the table to those keys, and the filter also matches variable names. See [References](configuration.md#references).

## Notifications

Persisted notifications for attention requests, tool errors, crashed sessions and pending vault confirmations, grouped by day. Mark as read or dismiss, individually or all at once. Once an attention request or vault confirmation is settled (in the dashboard, by a timeout, or elsewhere), its row keeps its place and shows the outcome as a small pill (**resolved**, **expired**, or **closed** when the agent stopped waiting), and a toast still on screen for it closes. See [Notifications](notifications.md).

**Channels** (Notifications → Channels) sends notifications to your phone through Telegram, Discord, ntfy or a webhook: one card per channel with its status, last delivery and 24-hour counts, and an **Add channel** wizard with a live preview and a test message. **Delivery log** lists every send, edit and delete, and says why anything was not sent. See [Channels](notifications.md#channels).
Loading
Loading