feat([wire-proactive-cos-speech-to-real-triggers]): proactive CoS speech on error/task/notification events#476
Merged
atomantic merged 5 commits intoMay 24, 2026
Conversation
…S speech to error/task/notification events Wire speakProactive to three live event sources via a new proactiveTriggers module, called once from socket.js: - errorEvents 'error' (critical severity only) - cosEvents 'task:ready' - notificationEvents 'added' (high/critical priority only) Each source has an independent rate-limit bucket; the bucket only advances when a line actually goes out (a quiet-hours/disabled suppression doesn't consume the budget). Listeners stay synchronous and route awaited synthesis through a never-rejecting dispatch so a TTS failure can't surface as a process-killing unhandled rejection.
…md and log to changelog
…le catch to a single error boundary /simplify: the inner speak().catch plus the fire() backstop were one catch too many. Keep the single explicit boundary catch at the fire-and-forget call site (the documented async-listener pattern) and let synthesis rejections propagate to it.
… concurrent same-source bursts
Contributor
There was a problem hiding this comment.
Pull request overview
Wires proactive Chief-of-Staff (CoS) voice output to live server-side events so the assistant can speak first on critical errors, newly-ready tasks, and high-priority notifications, using the existing speakProactive delivery path.
Changes:
- Added
wireProactiveTriggers()to subscribe toerrorEvents,cosEvents, andnotificationEvents, with per-source rate limiting and rejection containment. - Added a comprehensive Vitest suite covering helper logic, wiring behavior, throttling, and unhandled-rejection safety.
- Wired proactive triggers once from
server/services/socket.jsduring socket initialization, and updated planning/changelog docs.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| server/services/voice/proactiveTriggers.js | New trigger wiring + formatter/rate-limit helpers + fire-and-forget dispatch with rejection containment. |
| server/services/voice/proactiveTriggers.test.js | New unit/wiring tests validating filtering, throttling, bucket behavior, and unhandled rejection guard. |
| server/services/socket.js | Initializes proactive trigger wiring alongside other event-forwarding setup. |
| PLAN.md | Removes the completed plan item for wiring proactive triggers. |
| .changelog/NEXT.md | Adds release-note entry describing proactive voice triggers and rate limiting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…lly the unhandledRejection listener cleanup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wires proactive Chief-of-Staff speech to real subsystem events. The delivery
plumbing (
POST /api/voice/speak+ thevoice:speaksocket event +speakProactive) already existed; this connects it to live triggers so theassistant can speak first instead of only replying to a user turn.
A new
server/services/voice/proactiveTriggers.jssubscribes to three eventsources and turns select events into spoken lines, wired once from
socket.jsalongside the other
setup*EventForwarding()calls:errorEvents'error'— critical severity only (routine 4xx/5xx are skipped)cosEvents'task:ready'— a new task became spawnablenotificationEvents'added'— high / critical priority onlyRate limiting
Each source has its own min-interval bucket (
error90s,task:ready60s,notification60s). The check runs beforespeakProactive, so a throttledevent skips the config read + synthesis cost. A bucket only advances when a
line actually goes out — a quiet-hours / disabled suppression doesn't consume
the budget, so the next real event can still try.
Safety
EventEmitterdoesn't await async listeners, so a rejected synthesis wouldsurface as a process-killing unhandled rejection (Node ≥15). The listeners stay
synchronous and call dispatch fire-and-forget through a single explicit
.catchboundary — a TTS failure logs and is contained. All proactive output still
flows through
speakProactive, which respects quiet hours and thellm.proactive.enabledswitch (so this is a no-op when proactive voice is off).Testing
proactiveTriggers.test.js— 18 tests covering the pure helpers(rate-limit predicate, priority gate, formatters) and the wiring (severity /
priority filtering, per-source throttle, bucket-only-advances-on-send, and a
load-bearing assertion that a rejecting
speakleaks no unhandled rejection).