Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .changeset/notification-contract-outbox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"browserhive": minor
---

Notifications now follow what they announce, and the groundwork for sending them to your phone is in place.

- **A notification keeps its place as things change.** When you resolve an attention request or a vault confirmation, reject it, or it times out, its notification shows the outcome (**resolved**, **expired**) as a small pill in the bell and on the Notifications page, instead of staying as if it were still waiting. A toast still on screen for it closes. A recovered subsystem marks its degradation notification resolved the same way.
- **Richer notification data in the API.** `GET /api/v1/notifications` and the `notifications` WebSocket topic add `kind`, `category`, `severity`, `state`, `revision` and `thread` to every notification. Existing fields are unchanged.
- **Safer text.** An agent's attention reason and other text copied into a notification now go through the same redaction as the logs, and page addresses lose their query strings.
- **Foundations for Telegram, Discord and ntfy.** Every notification is also a versioned message document, whose JSON Schema is published in the reference docs. Delivery to chat apps goes through a new outbox in the database, with retries and a circuit breaker, so nothing is lost when a service is down. No channel can be configured yet; with none configured nothing changes and nothing extra runs.
- The database upgrades on start (schema v5: new columns on notifications and three new tables; a backup is written first). Older releases can still open it. Existing notifications are classified from what they already recorded; nothing is invented for them.
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +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
- [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 All @@ -33,6 +34,7 @@ Generated from the source by `scripts/gen-docs.ts`; always in sync with the code
- [REST API](reference/api.md): every admin API endpoint with scope and authentication
- [WebSocket protocol](reference/websocket.md): envelope, topics, commands, screencast frames
- [Config file JSON Schema](reference/config.schema.json)
- [Notification message JSON Schema](reference/notification-message.schema.json)

## Contributing

Expand Down
3 changes: 2 additions & 1 deletion docs/contributing/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Playwright and Patchright may be imported only under `infra/browsers/`; Kysely a
- **Auth:** provider chain (password session cookie, bearer token, short-lived grant), `Authorizer.can(principal, scope)`, ownership on every tool (D-09).
- **Vault:** broker with fixed gate order and one audit row per fill, Bitwarden backend, bindings and folder policies in SQLite (D-14).
- **Operator requests:** one broker for attention and vault confirmations with deadlines, lease pause, cancellation and restart recovery (D-15).
- **Notifications:** pure producer rules turn bus events into rows plus a versioned `NotificationMessage` (`@browserhive/contracts/notifications`) with full-state revisions; the in-app inbox is delivered inline, external channels through a transactional outbox (`notification_deliveries`) drained by a worker with retries, coalescing, a circuit breaker that never raises a degradation, and a TTL sweep. Platform adapters implement `NotificationChannel` and receive only the contract after `restrictContent` and `degrade` (D-16, D-32, D-34, [spec 03 §9](../../specs/03-admin-backend.md#9-notifications-d-16-d-32-d-34)).
- **Observability:** structured logger with per-module levels and a ring buffer, OpenTelemetry API everywhere with exporters opt-in, redaction by construction (D-08, D-20, [spec 10](../../specs/10-error-handling-and-telemetry.md)).
- **Event bus:** typed in-process events (`session.opened`, `tool.called`, `page.visited`, `vault.access`, …). Every mutation publishes through it; the WebSocket layer never synthesizes events.

Expand All @@ -88,7 +89,7 @@ A failure in phase N unwinds phases N-1…1 in reverse and exits with a typed bo

## Extension points

Seams exist for features that are deliberately not built yet: other browser engines (`BrowserDriver` capabilities), managed proxies (`ProxyResolver`, `LaunchSpec.proxy`), other vault backends (`VaultBackend`), security intercepts and approval gates (`OperatorRequestBroker.kind`, `InterceptionChain`), multi-user auth (`AuthenticationProvider`, `tenant_id`), external notification channels (`NotificationChannel`), resource governance (`AdmissionPolicy`). See [spec 01 §9](../../specs/01-overall-architecture.md#9-extension-points-seams-that-exist-without-their-features).
Seams exist for features that are deliberately not built yet: other browser engines (`BrowserDriver` capabilities), managed proxies (`ProxyResolver`, `LaunchSpec.proxy`), other vault backends (`VaultBackend`), security intercepts and approval gates (`OperatorRequestBroker.kind`, `InterceptionChain`), multi-user auth (`AuthenticationProvider`, `tenant_id`), external notification channels (`NotificationChannel` plus a factory per channel kind in `ChannelRegistry`), resource governance (`AdmissionPolicy`). See [spec 01 §9](../../specs/01-overall-architecture.md#9-extension-points-seams-that-exist-without-their-features).

## Working on the code

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ Version, transport, uptime, bind address, sessions live versus the cap, open att

## 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.
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).
51 changes: 51 additions & 0 deletions docs/guide/notifications.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Notifications

BrowserHive tells you when something needs you or went wrong: an agent asked for help, a vault fill waits for your approval, a session crashed, tools keep failing, or BrowserHive itself is degraded. Notifications are stored in the database, so they survive reloads and restarts, and they appear in the dashboard's bell, as toasts and on the **Notifications** page.

This page explains what produces a notification, how one changes over its life, and the delivery machinery that sends notifications to chat apps such as Telegram, Discord and ntfy. That delivery is being built in stages: this release lays the foundations, and the first channels arrive next.

## What BrowserHive notifies about

| What happened | Kind | Category | Severity |
|---|---|---|---|
| An agent called `request_attention` ([human takeover](attention.md)) | `attention.requested` | needs you | warn |
| A vault fill waits for your confirmation ([vault](vault.md)) | `vault.confirm` | needs you | warn |
| A session crashed | `session.crashed` | problems | error |
| A session was reaped because its lease expired | `session.reaped` | problems | warn |
| Tools failed (grouped per session: "shop · 12 tool errors") | `tool.errors` | problems | warn |
| A subsystem is degraded (for example the retention sweep failed) | `system.degraded` | system | error |
| A notification channel keeps failing | `channel.broken` | system | error |

Routine events are deliberately silent: a session opening, a page visit, a clean close. Agents cannot send notifications themselves: every notification comes from something BrowserHive observed. The `request_attention` tool is how an agent reaches you.

## A notification has a life

A notification keeps its identity while the thing it announces changes. When you resolve an attention request, reject it, or it times out, the same notification moves to **resolved** or **expired** instead of a second one appearing. The dashboard shows that outcome as a small pill on the row (**resolved**, **expired**, or **closed** when the agent stopped waiting) and closes the toast if it is still on screen. The row keeps its place in the list. A growing group of tool errors updates its count in place.

Every change is a new **revision** of the notification's message. Each revision is complete, so whoever shows it never has to merge changes. That is also what lets a chat message be edited in place later, silently: only a new notification makes noise.

Notifications from before this release keep working; they get the new fields, derived from what they already recorded, and nothing is invented for them.

## The message contract

Every notification is also a `NotificationMessage`: a small, versioned JSON document with a title and summary, structured blocks (text, facts, lists, tables, images, code), up to five buttons, and what it is about (session, tool, error code). Links in it are dashboard paths. BrowserHive owns this contract; every channel renders it and none reads BrowserHive's internals. The JSON Schema is published at [notification-message.schema.json](../reference/notification-message.schema.json), so you can build your own consumer from the generic webhook channel when it arrives.

Before a message is stored or sent, BrowserHive removes known secrets and credential-shaped text from every field and strips query strings from URLs, the same redaction the logs get ([security](security.md)). A channel can be set to carry less: only titles and facts, or only counts.

## How delivery to other apps works

Delivery is designed so that nothing is silently lost and nothing waits on a slow chat service:

- **An outbox in the database.** When a notification is created or changes, the jobs that deliver it to each channel are written in the same database transaction. A worker sends them afterwards. If BrowserHive stops mid-way, it picks up at the next start. This guarantees at-least-once delivery: an edit or a delete can safely be repeated, and in the rare case of a crash in the middle of sending a new message, that message can arrive twice.
- **Edits instead of spam.** When a notification changes, its chat message is edited in place, at most once every 3 seconds. Edits never make a sound.
- **Retries.** A failed send is retried with growing pauses, honouring the platform's "retry after". It gives up after 8 attempts or 24 hours.
- **A circuit breaker.** After 5 failures in a row, the channel is marked **broken**, you get an in-app notification about it, and its deliveries pause. A failing channel is never reported as a BrowserHive degradation: that report would be sent through the same failing channel.
- **Catching up.** After an outage only the latest state of each notification is sent, and a pile of routine updates collapses into one message that says how many you missed.
- **A log for every decision.** Each delivery is logged, including the ones a channel's rules filtered out and why, so "why didn't I get it?" always has an answer. The log is kept for 30 days.
- **Messages that clean up after themselves.** A channel can delete its messages after a time you choose per category, or once they are resolved. BrowserHive does the deleting, because no chat platform offers a timer for bot messages. The default is to keep everything.

Your own accounts, no servers: BrowserHive never runs a relay or a shared bot. You create your own Telegram bot, Discord webhook or ntfy topic, and every connection goes out from your machine. Tokens stay in environment variables; BrowserHive stores only the variable names, so a database backup never contains a token.

## Retention

Read or dismissed notifications are kept for 30 days, others for 90. Delivery history is kept for 30 days. Configured channels are never pruned; `browserhive purge` lists them with everything else in the database.
1 change: 1 addition & 0 deletions docs/guide/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Failures are returned as a status and reason (`origin_mismatch`, `not_authorized
- **Traces do not contain typed credentials.** Playwright tracing is stopped before the first credential keystroke and restarted after submit, and the parts are merged when the session closes. The replay has a gap instead of the login POST.
- **Pixels are not redacted.** The live view and screenshots show exactly what the browser shows. Screenshot tracing skips frames while a redaction window is open, but operators are trusted with the live view.
- Every secret BrowserHive creates or handles (seed password, tokens, cookies, vault session tokens, OTLP headers) is registered with a redactor that scrubs logs, database rows, WebSocket frames, MCP results and OTLP exports. That includes secrets the config file reads from environment variables (`{env:CI_TOKEN}`, see [References](configuration.md#references)): the variable's name is shown so you know what to set, never its value or the file's text around it.
- **Notifications are redacted before they are stored or sent.** Every text a notification copies from an event (an agent's attention reason, a page address, an error) goes through the same redactor, and URLs lose their query strings. A notification channel keeps its tokens in environment variables; BrowserHive stores only the variable names, so a database backup never contains one. See [Notifications](notifications.md).

## What is recorded

Expand Down
2 changes: 2 additions & 0 deletions docs/guide/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ browserhive db migrate

**Each session records whether it ran sandboxed (schema v4).** From 0.2 on, the database stores each session's sandbox state and browser version when its browser launches, so closed sessions keep showing them. The migration only adds columns: an older release still opens the database. Sessions from before the upgrade show "not recorded"; nothing is guessed for them.

**Notifications gain a message contract (schema v5).** The database adds the notification contract's fields to every notification (kind, category, severity, state, revision, thread) and three tables for delivery to chat apps (channels, the delivery outbox and the sent-message index). Existing notifications are classified from what they already recorded; an attention request's notification reads as resolved or expired when the request was. The migration only adds columns and tables: an older release still opens the database. See [Notifications](notifications.md).

Prereleases are published under the `next` tag: `bun add -g browserhive@next`.

## Downgrade
Expand Down
Loading
Loading