Skip to content
Open
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ WebBrain accepts slash commands as the first thing on a line in the input box. T
| `/help` | Show the list of available commands |
| `/schedule [prompt]` | Create a scheduled task, optionally prefilling its prompt |
| `/schedule --list` | Show scheduled tasks |
| `/watch [--keep] [--secs <30-120>] [--long \| --short] <condition and action> [/beep]` | Poll the current web page for a condition; stop after the first match unless `--keep` is set, and optionally play a background alert |
| `/progress` | Show the current progress ledger |
| `/scratchpad` | Show the current scratchpad |
| `/scratchpad --append <text>` | Append text to the current scratchpad |
Expand Down Expand Up @@ -372,6 +373,17 @@ WebBrain accepts slash commands as the first thing on a line in the input box. T
| `/dev` | Switch to Dev mode before sending |
| `/plan` | Switch to Ask mode with planning intent |

`/watch` performs its first check immediately and then polls every 60 seconds
by default. Relative conditions such as “when a new commit appears” establish a
baseline on the first check; absolute conditions such as “when CI is green” can
match immediately. `--secs` accepts 30–120 seconds. A trailing `/beep` enables
the watch-only alert tool; `--short` and `--long` select its tone. Alerts play
only after a verified successful action, and `--keep` suppresses repeated
alerts for the same stable event key. Polls run in dedicated inactive tabs, so
leaving the initiating page does not navigate it back to the watched URL. If a
model verifies the action but omits the optional alert tool, the watch records
the warning and completes or continues without sound.

Configuration snapshots use the `webbrain-config/1` schema and include all
portable Settings values, including provider, vision, transcription, and
CapSolver API keys, profile data, user memory, custom skills, and permission
Expand Down
23 changes: 23 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ The scheduler lets the agent defer work to a future browser session using the br
|---|---|---|
| `resume` | `schedule_resume` tool | Continues the current conversation in the same tab at a future time. Terminal tool — the current run ends when it fires. |
| `task` | `schedule_task` tool | Runs a standalone user-authored prompt at a future time, optionally recurring. |
| `task` with `source: "watch"` | `/watch` slash command | Polls the initiating page URL in a dedicated inactive tab at a fixed 30–120 second interval. |

**Job lifecycle**

Expand Down Expand Up @@ -561,6 +562,28 @@ pending → running → completed
- `once` — fires at a single `run_at` or `after_seconds` time. `after_seconds: 0` starts the task immediately.
- `recurring` — fires repeatedly at `interval_minutes` (1 min – 1 year); after each run completes, `nextRunAt` is advanced and the next alarm is set.

**Conditional watches**

Watch jobs use `interval_seconds` (60 by default, 30–120 allowed) and run their
first check immediately. Each poll carries the previous observation inside an
untrusted-content boundary. A `partial` outcome records that observation as the
next baseline and schedules another poll. A successful one-shot watch
completes; `--keep` schedules another poll only for future distinct events. A
failed check/action or a run without an explicit `done` outcome stops the watch
rather than looping silently.

The optional trailing `/beep` flag exposes a scoped
`beep({event_key, message?})` tool only during that watch. It arms an alert
before the requested action and reports a duplicate key before an action can be
repeated; it does not play audio itself. The scheduler first persists a fresh
event key and requires verified `done(outcome="success")`. Only then does the
Chrome offscreen document or Firefox background page generate the selected
default, short, or long tone. If a model verifies success but omits `beep`, the
scheduler records a warning and completes or continues the watch without audio
instead of discarding the successful action. Watch helper tabs are never forced
back to the URL after they diverge; the next poll creates a fresh inactive helper.
Playback follows the existing `notifySound` setting.

**Persistence**

Jobs are stored in `chrome.storage.local` under the key `wb_scheduled_jobs` as a JSON array. On background restart, any jobs in `running`/`needs_user_input` are demoted to `queued` and retried, so no run is silently lost.
Expand Down
4 changes: 2 additions & 2 deletions docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ Differences below.)
| `debugger` | CDP access provides trusted events and full DOM/network control on any tab | The debugger is only attached during active agent runs and detached on completion/abort. |
| `webRequest` | Can observe XHR/fetch metadata for requests made by the active page | API mutation observer is off by default; when enabled, it keeps only a bounded in-memory per-tab buffer for repeated-click shortcut hints and opaque same-origin replay. |
| `downloads` | Can save files to the user's Downloads folder without prompting | Only the agent's explicit download-capable tool calls (`download_files`, `download_file`, `download_resource_from_page`, `download_social_media`, download-job skill tools) use this, and each is gated by the capability × origin permission prompt. |
| `alarms` | Can wake scheduled jobs in future browser sessions | Only `schedule_resume` / `schedule_task` create alarms, and those tools are gated. |
| `offscreen` | An offscreen document can make HTTP requests immune to user CSP | Only used for localhost LLM provider proxy and tab recording. Never forwards arbitrary URLs. |
| `alarms` | Can wake scheduled jobs in future browser sessions | `schedule_resume` / `schedule_task` are gated; the user-authored `/watch` slash command can also create a page-bound 30–120 second conditional poll. |
| `offscreen` | An offscreen document can make HTTP requests immune to user CSP or play audio without an open panel | Used for the localhost LLM proxy, tab recording, validated download staging, the local cloud bridge, and successful `/watch /beep` tones. Watch audio receives only a style selector, never page content or an arbitrary URL. |

### Authentication

Expand Down
43 changes: 43 additions & 0 deletions src/chrome/src/agent/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,12 @@ export class Agent {
this.scheduledRunPolicies.set(tabId, {
requireConsequentialConfirmation: policy?.requireConsequentialConfirmation !== false,
autoApprovePlanReview: policy?.autoApprovePlanReview === true,
watch: policy?.watch?.beep === true ? {
beep: true,
beepStyle: ['long', 'short'].includes(policy.watch.beepStyle) ? policy.watch.beepStyle : 'default',
lastTriggeredEventKey: String(policy.watch.lastTriggeredEventKey || '').slice(0, 200) || null,
armedEventKey: null,
} : null,
});
}

Expand Down Expand Up @@ -13287,6 +13293,39 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
if (name === 'done_json') {
return handleDoneJson(this.cloudRunContexts.get(tabId), args);
}
if (name === 'beep') {
const watch = this.scheduledRunPolicies.get(tabId)?.watch;
if (watch?.beep !== true) {
return {
success: false,
denied: true,
armed: false,
error: 'beep is available only during a /watch run created with /beep.',
};
}
const rawEventKey = typeof args?.event_key === 'string' ? args.event_key.trim() : '';
if (!rawEventKey || rawEventKey.length > 200) {
return { success: false, armed: false, error: 'event_key must contain 1-200 characters.' };
}
if (watch.armedEventKey && rawEventKey !== watch.armedEventKey) {
return {
success: false,
armed: false,
error: `This watch run already armed event_key "${watch.armedEventKey}". Finish that event before another poll handles a different key.`,
};
}
const message = typeof args?.message === 'string' ? args.message.trim().slice(0, 300) : '';
const duplicate = rawEventKey === watch.lastTriggeredEventKey;
if (!duplicate) watch.armedEventKey = rawEventKey;
return {
success: true,
armed: !duplicate,
duplicate,
eventKey: rawEventKey,
message: message || null,
beepStyle: watch.beepStyle,
};
}
const protectedFailure = await this._chromeProtectedPageFailure(tabId, name);
if (protectedFailure) return protectedFailure;
if (name === 'list_webmcp_tools') {
Expand Down Expand Up @@ -18588,6 +18627,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
skillTools,
cloudRun: !!cloudRunContext,
outputSchema: cloudRunContext?.outputSchema || null,
watchBeep: this.scheduledRunPolicies.get(tabId)?.watch?.beep === true,
});
let allowedToolNames = new Set(tools.map(t => t.function.name));
const plannerTemperature = this._isActionMode(mode) ? 0.15 : 0.3;
Expand Down Expand Up @@ -18752,6 +18792,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
skillTools,
cloudRun: !!cloudRunContext,
outputSchema: cloudRunContext?.outputSchema || null,
watchBeep: this.scheduledRunPolicies.get(tabId)?.watch?.beep === true,
});
allowedToolNames = new Set(tools.map(t => t.function.name));

Expand Down Expand Up @@ -19248,6 +19289,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
skillTools,
cloudRun: !!cloudRunContext,
outputSchema: cloudRunContext?.outputSchema || null,
watchBeep: this.scheduledRunPolicies.get(tabId)?.watch?.beep === true,
});
let allowedToolNames = new Set(tools.map(t => t.function.name));
const plannerTemperature = this._isActionMode(mode) ? 0.15 : 0.3;
Expand Down Expand Up @@ -19285,6 +19327,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
skillTools,
cloudRun: !!cloudRunContext,
outputSchema: cloudRunContext?.outputSchema || null,
watchBeep: this.scheduledRunPolicies.get(tabId)?.watch?.beep === true,
});
allowedToolNames = new Set(tools.map(t => t.function.name));

Expand Down
Loading
Loading