Skip to content

feat: add conditional /watch polling and beep alerts#447

Open
alectimison-maker wants to merge 6 commits into
webbrain-one:mainfrom
alectimison-maker:feat/watch-command
Open

feat: add conditional /watch polling and beep alerts#447
alectimison-maker wants to merge 6 commits into
webbrain-one:mainfrom
alectimison-maker:feat/watch-command

Conversation

@alectimison-maker

@alectimison-maker alectimison-maker commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add /watch [--keep] [--secs <30-120>] [--long | --short] <condition and action> [/beep] for immediate, page-bound conditional polling
  • extend the durable scheduler with watch metadata, baseline-aware partial polls, one-shot/keep outcome handling, safe untrusted observation replay, and duplicate coalescing that remains stable after the first poll
  • run watches in dedicated inactive helper tabs; a diverged helper is replaced instead of navigating the initiating/current tab back to the watched URL
  • reuse one sticky assistant message per watch and replace it with the latest poll result, avoiding blank bubbles and stale observations
  • expose a scoped beep({event_key, message?}) tool only to /beep watches; persist and dedupe event keys before background audio, and play only after verified done(outcome="success")
  • support distinct short/default/long tones through Chrome MV3 offscreen audio and the Firefox background page, honoring notifySound without a second side-panel chime
  • surface watch interval/mode/alert/last-observation/warning state in scheduled cards, localize command/status copy across all shipped locales, and document behavior/security boundaries

Safety properties

  • prior page-derived observations are nonce-delimited and boundary markup is stripped before the next poll
  • ordinary agent runs cannot see or invoke beep; one poll can arm only one fresh event key
  • duplicate, partial, failed, and missing-outcome runs do not play an alert
  • a verified successful action that omits the optional beep arm completes or continues without sound and records a warning; it does not destroy a --keep watch
  • a fresh event key is persisted before best-effort audio playback
  • watch polling never forces a user-controlled tab back to the watched URL

Tests

  • node test/run.js: 1269 passed, with 1 pre-existing main failure (CHANGELOG 25.8.0 vs package 25.8.5)
  • npm run test:security: 60/60 passed
  • npm run test:fixtures: 125/125 passed
  • syntax checks pass for every modified JavaScript file; git diff --check passes

Closes #410

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

@alectimison-maker is attempting to deploy a commit to the esokullu's projects Team on Vercel.

A member of the Team first needs to authorize it.

},
scheduledAt: createdAt,
nextRunAt: iso(this.now() + 1000),
immediate: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug] Watch jobs are created with immediate: true and nextRunAt ≈ now+1s, then _completeWatch sets immediate: false and advances nextRunAt by the watch interval. sameScheduledIntent still requires matching scheduledJobIsImmediate and near scheduled times, so after the first poll an identical /watch is no longer treated as a duplicate live job. Users can stack many concurrent Act-mode watches for the same page/condition (amplified by 30–120s intervals and LLM cost).

Suggestion: For source === 'watch', dedupe live jobs by target + watch payload only (ignore immediate / near-time). Extend the existing “identical live watch should be deduped” test to re-create after one successful partial poll.

} else if (event === 'completed') {
ensureScheduledTerminalMessage(job);
await settleScheduledRun(event, job, runTabId);
} else if (event === 'polled' || event === 'triggered') {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug] Keep watches emit running on every poll, and the existing handler always does addMessage('assistant', '') with the same dataset.scheduledJobId. The new polled/triggered path only settles the first matching assistant node, and only fills text when that node is still empty. Result: each 30–120s poll leaves an additional empty assistant bubble in chat, while later observations never replace the first filled one.

Suggestion: For job.source === 'watch' on running, reuse findScheduledAssistantMessageForJob(jobId) (or a single sticky run row) instead of creating a new message; on polled/triggered, update that message’s text to the latest lastResult rather than gating on !textEl.textContent.trim().

Comment thread src/chrome/src/agent/scheduler.js Outdated
target: {
type: 'url',
url: parsed.url,
...(tabId != null ? { tabId } : {}),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug] createWatchJob stores the initiating tabId on a type: 'url' target. Existing _resolveTab will tabs.update that same tab back to the watched URL whenever the tab has navigated away. At 30–120s this repeatedly hijacks the user’s active browsing session (and can wipe SPA state) instead of following the page or using a background tab.

Suggestion: Prefer a dedicated inactive tab for watches: omit initiating tabId from the URL target (always tabs.create({ active: false })), or if URL diverges create/reuse a background tab rather than forcing tabs.update on the original tab. Document the chosen binding semantics in README/architecture.

Comment thread src/chrome/src/agent/scheduler.js Outdated
&& !!eventKey
&& !duplicateAlert
&& eventKey !== job.watch?.lastTriggeredEventKey;
const alertContractFailed = lastOutcome === 'success'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] For /beep watches, any done(outcome="success") without a fresh armed event_key permanently fails the entire job (alertContractFailed). That is intentionally fail-closed for audio, but local/small models frequently omit optional tools; a single miss destroys a long-running --keep watch after it may already have performed the user-requested action.

Suggestion: Consider treating missing arm as a non-fatal “success without alert” (complete/continue without sound, record a warning), or only fail closed when beep was armed with a key that fails the contract. Keep the stronger fail-closed path if product intent is strict, but call it out in the slash help / created message.

@@ -2464,7 +2488,10 @@ async function settleScheduledRun(event, job, tabId = currentTabId) {
if (assistantEl) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] Settlement still only injects job.lastResult when the assistant text is empty. Even after Issue 2 is fixed to reuse one bubble, keep-watch users will not see refreshed observations in chat unless this condition is relaxed for polled/triggered.

Suggestion: For watch poll events, always replace/update the message text with the latest observation (still via formatMarkdown), and keep the empty-only gate for true terminal schedule completions if desired.

@@ -297,6 +298,13 @@ export default {
"sp.slash.record_transcribe": "Record and save a Whisper transcript after stop",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] /watch is outOfBand: true and works while busy, but sp.slash.busy_only_oob still lists only the older OOB commands and does not mention /watch.

Suggestion: Add /watch to the busy-only OOB toast string in all locales (Chrome and Firefox).

# Conflicts:
#	src/chrome/src/agent/agent.js
#	src/chrome/src/ui/sidepanel.js
#	src/firefox/src/background.js
#	src/firefox/src/ui/sidepanel.js
@alectimison-maker

Copy link
Copy Markdown
Contributor Author

Addressed all six review points in 70b8b4e8 after merging the latest main:

  • watch dedupe now ignores drifting immediate/time fields and is tested after a real partial poll
  • each watch reuses one sticky assistant message and replaces it with the latest observation
  • watches no longer bind or navigate the initiating tab; dedicated helpers stay inactive and diverged helpers are replaced
  • a successful run that misses the optional beep arm now completes/continues without sound and records a warning
  • /watch is included in the busy OOB message for every Chrome/Firefox locale (including locales added since the original PR)
  • README and architecture docs now describe helper-tab and missed-alert semantics

Validation: node test/run.js 1269/1270 (only the current-main CHANGELOG 25.8.0 vs package 25.8.5 mismatch), security 60/60, fixtures 125/125. The GitHub smoke/WebMCP E2E check is green. The remaining Vercel status is the team authorization check, not a code/test failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add /watch shorthand with conditional /beep notifications

2 participants