feat(notifications): agent spoken narration via speak tool#3060
feat(notifications): agent spoken narration via speak tool#3060jonmcwest wants to merge 5 commits into
Conversation
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
|
|
Addressed both Greptile P2 comments in dd25b52:
Also fixed the two failing CI checks: added |
e6a7b7c to
9e0bf61
Compare
pauldambra
left a comment
There was a problem hiding this comment.
i'd be happy to approve this since i've seen it run but it's big enough we should probably get team code to review it
i think it has the natural next step of hooking up speech to text so you can reply when ph-ode talks to you
Add a `speak` local tool the agent calls to narrate out loud: when it finishes a request, hits a meaningful new phase, or is blocked and needs the user. The tool is a no-op agent-side; the desktop renderer observes the surfaced tool_call and routes it to a core SpeechQueueService. SpeechQueueService is portable orchestration living in core: it serializes utterances so parallel sessions never talk over each other, coalesces stale per-task narration, lets needs-user lines jump the queue (and never drops them), and caps the queue so a burst can't back up minutes of speech. It depends only on the host-neutral ISpeech capability plus injected settings/name providers. The app owns the "PostHog Code task '…' —" prefix and needs-user "Hey <name>," greeting via composeUtterance, so the agent supplies only content. Playback goes through an ElevenLabs adapter with a macOS `say` fallback; users control which kinds are spoken (and can mute progress) in Notifications settings. Generated-By: PostHog Code Task-Id: 6912b099-cb75-4f41-a337-1d2275c4be66
The `speak` "done" case was getting skipped: once the model writes its
text answer the turn feels delivered and it stops before the trailing
speak call. Advisory phrasing ("lean toward using it") lost to that
default.
Reframe as a hard end-of-turn rule and, crucially, tell the model to
call speak BEFORE its final text reply — mid-turn, while still in
tool-calling mode — which is the only reliable ordering.
Generated-By: PostHog Code
Task-Id: 6912b099-cb75-4f41-a337-1d2275c4be66
- Add enqueueSpeech to the cloud-task-update notification test harness deps so SessionService no longer throws "enqueueSpeech is not a function". - Allowlist the host-resident ElevenLabs speech service (the API key stays in the host process, matching secure-store/encryption), fixing the host boundary check. - composeUtterance: run the task-prefix guard before greeting injection so an already-prefixed agent line can't be double-prefixed when addressing by name; add a regression test. - Key speakCalls by taskRunId so a session teardown drops any speak calls still mid-stream instead of leaking them until reset(). Generated-By: PostHog Code Task-Id: 3553940c-2b6e-41dd-bc02-27f5df786a29
Adding the always-enabled `speak` tool to LOCAL_TOOLS means the local-tools MCP server is now built on every session (desktop and cloud), not just when a cloud-only git tool's gate passes. - claude local-tools.test: a desktop run now exposes `speak` (and still omits the cloud-only signed-git tools), so assert that instead of an undefined server. - codex-agent.test: default the mocked existsSync to true in beforeEach so the always-on local-tools MCP script resolves; the "no structured-output" no-op cases now assert `posthog_output` is absent rather than the whole mcpServers array being empty; and the injection case looks up posthog_output by name since the local-tools server is also present. Generated-By: PostHog Code Task-Id: 3553940c-2b6e-41dd-bc02-27f5df786a29
Consecutive permission requests each spoke "needs your input" even while the user was staring at the chat clicking Approve: needs_input bypassed focus routing unconditionally. Tag each speech request with its source — the agent's intentional speak tool call vs the deterministic turn/permission backstop. Backstop lines never play when focus routing says "suppress" (app focused, viewing that task); agent lines keep current behavior, including the needs_input focus bypass. A prompt on a task you're not viewing still speaks. Also adds the enqueueSpeech fake missing from the cloud-notification test harness since the speak feature landed. Generated-By: PostHog Code Task-Id: 6912b099-cb75-4f41-a337-1d2275c4be66
9e0bf61 to
8818df8
Compare
What
Gives the agent a voice. A new
speaklocal tool lets the agent narrate out loud — when it finishes your request, reaches a meaningful new phase, or is blocked and needs you (a question, decision, confirmation, or an error only you can resolve). You hear it even when you're in another tab.How it fits the architecture
The flow follows the same "pure side-effect off the event stream" pattern as completion/permission notifications:
speaktool (packages/agent) — no-op agent-side (the agent subprocess/sandbox can't reach your speakers). It just surfaces atool_callcarrying the text + kind.SpeechQueueService(packages/core) — portable orchestration. Serializes utterances so parallel sessions never talk over each other, coalesces stale per-task narration, letsneeds_inputlines jump the queue (never dropped), and caps the queue so a burst can't back up minutes of speech. Depends only on the host-neutralISpeechcapability + injected settings/name providers — no tRPC, Node, or Electron.composeUtterance(packages/core) — the app owns thePostHog Code task '…' —prefix and theHey <name>,greeting for needs-user lines, so the agent supplies only content. Idempotent against an agent that already added a prefix/greeting.ISpeechadapter (apps/code) — ElevenLabs playback with a macOSsayfallback (audio tags like[laughs]are stripped by the fallback).progress), pick a voice, and set the API key in Notifications settings.Testing
packages/corespeech + composeUtterance: 18 tests passpackages/uispeechRouting: 12 tests passpackages/agentspeak tool: 11 tests passpnpm typecheckclean across all 22 packagesnoRestrictedImportsviolations in core)Created with PostHog Code