Skip to content

feat(web): audible ping when an agent calls ping_user - #90

Merged
ukanwat merged 3 commits into
mainfrom
worktree-ping-sound
Jun 26, 2026
Merged

feat(web): audible ping when an agent calls ping_user#90
ukanwat merged 3 commits into
mainfrom
worktree-ping-sound

Conversation

@sandydasari

Copy link
Copy Markdown
Owner

What

Plays a short synthesized two-tone sound in the web UI whenever an agent invokes the ping_user tool, so the user stays aware of pings on any page — not just the chat view.

How

  • apps/web/app/lib/sound.ts (new) — playPing() via the Web Audio API. No audio asset to ship, works offline, lazy AudioContext with resume-on-suspended to cope with the browser autoplay policy.
  • apps/web/app/lib/usePingSound.ts (new) — subscribes to the workforce-wide /api/home/stream SSE channel and fires playPing() on task_events whose event.kind === "ping_user", deduped by event id (an SSE reconnect could redeliver).
  • apps/web/app/routes/__root.tsx — calls usePingSound() in RootLayout, which wraps every route, so the sound is global rather than scoped to the home/chat page where useHomeStream lives.

Deliberately fires only on explicit ping_user events, not on every turn ending (that would be noisy).

Verification

check-types + lint pass (also enforced by the pre-commit/pre-push hooks — 26 tests passed on push). Verified live in a browser against the ~/.openacme-test slot on :3456:

  • The served module is this branch's code, with the ping_user filter + playPing call.
  • /api/home/stream opens from the page.
  • The real playPing() synthesizes audio without error, driven by the exact server wire-payload for a ping_user event; dedupe confirmed (same id doesn't replay).

The server path (ping_user tool → EventStore → broadcaster → streams.ts → home stream) was confirmed by code inspection. A fully LLM-originated ping wasn't exercised because the test slot's Anthropic OAuth token is expired (a test-env issue, unrelated to this change).

Note

First ping after a page load may be silent until the user has clicked once — inherent browser autoplay policy, not fixable in code.

🤖 Generated with Claude Code

Play a short synthesized two-tone sound whenever an agent invokes the
ping_user tool, so the user stays aware of pings on any page.

- sound.ts: playPing() via Web Audio (no asset, works offline, lazy
  AudioContext, resume-on-suspended for the autoplay policy).
- usePingSound.ts: subscribes to the workforce-wide /api/home/stream and
  fires playPing() on task_events with kind "ping_user", deduped by id.
- Mounted once in RootLayout so it fires regardless of the active route,
  not only on the home/chat view where useHomeStream lives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying openacme-ai with  Cloudflare Pages  Cloudflare Pages

Latest commit: a99ad8b
Status: ✅  Deploy successful!
Preview URL: https://b098447e.openacme-ai.pages.dev
Branch Preview URL: https://worktree-ping-sound.openacme-ai.pages.dev

View logs

@ukanwat

ukanwat commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Add in settings UI

Per PR review — the audible ping was always-on with no way to mute it.
Add a Sound card under Settings → Notifications with an on/off toggle
plus a Test-sound button. The preference persists in localStorage
(opt-out only; defaults on) and usePingSound reads it fresh at fire
time, so muting takes effect immediately on any open tab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sandydasari

Copy link
Copy Markdown
Owner Author

Added a Settings toggle (f1a5823). New Sound card under Settings → Notifications: on/off toggle + a Test-sound button. Preference persists in localStorage (opt-out only, defaults on); usePingSound reads it fresh at fire time so muting applies immediately on any open tab. Verified live on the test slot — toggle flips the badge/buttons, persists openacme.pingSound=off, no console errors.

baseUrl is deprecated for removal in TypeScript 7.0 (newer editor TS
already surfaces the warning). Since TS 5.0, "paths" resolves relative
to the tsconfig directory without baseUrl, so "baseUrl": "." is a
no-op here. Removing it clears the deprecation; check-types passes
unchanged for both apps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ukanwat
ukanwat merged commit d1dff49 into main Jun 26, 2026
2 checks passed
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.

2 participants