Skip to content

feat([wire-proactive-cos-speech-to-real-triggers]): proactive CoS speech on error/task/notification events#476

Merged
atomantic merged 5 commits into
mainfrom
claim/wire-proactive-cos-speech-to-real-triggers
May 24, 2026
Merged

feat([wire-proactive-cos-speech-to-real-triggers]): proactive CoS speech on error/task/notification events#476
atomantic merged 5 commits into
mainfrom
claim/wire-proactive-cos-speech-to-real-triggers

Conversation

@atomantic
Copy link
Copy Markdown
Owner

Summary

Wires proactive Chief-of-Staff speech to real subsystem events. The delivery
plumbing (POST /api/voice/speak + the voice:speak socket event +
speakProactive) already existed; this connects it to live triggers so the
assistant can speak first instead of only replying to a user turn.

A new server/services/voice/proactiveTriggers.js subscribes to three event
sources and turns select events into spoken lines, wired once from socket.js
alongside the other setup*EventForwarding() calls:

  • errorEvents 'error' — critical severity only (routine 4xx/5xx are skipped)
  • cosEvents 'task:ready' — a new task became spawnable
  • notificationEvents 'added' — high / critical priority only

Rate limiting

Each source has its own min-interval bucket (error 90s, task:ready 60s,
notification 60s). The check runs before speakProactive, so a throttled
event 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

EventEmitter doesn't await async listeners, so a rejected synthesis would
surface as a process-killing unhandled rejection (Node ≥15). The listeners stay
synchronous and call dispatch fire-and-forget through a single explicit .catch
boundary — a TTS failure logs and is contained. All proactive output still
flows through speakProactive, which respects quiet hours and the
llm.proactive.enabled switch (so this is a no-op when proactive voice is off).

Testing

  • New 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 speak leaks no unhandled rejection).
  • Full server suite green (332 files, 7369 passed, 7 skipped).

atomantic added 4 commits May 24, 2026 09:53
…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.
…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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 to errorEvents, cosEvents, and notificationEvents, 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.js during 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.

Comment thread server/services/voice/proactiveTriggers.js Outdated
Comment thread server/services/voice/proactiveTriggers.test.js Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@atomantic atomantic merged commit aa86a7f into main May 24, 2026
6 checks passed
@atomantic atomantic deleted the claim/wire-proactive-cos-speech-to-real-triggers branch May 24, 2026 17:11
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