#21566: feat(voice-call): bridge call transcripts to main agent session
channel: bluebubbles
channel: voice-call
channel: feishu
size: M
Cluster:
Voice Call Enhancements and Fixes
## Summary
When a voice call ends, the transcript is currently only persisted to the local call log (`calls.jsonl`). The main agent session has no visibility into what was discussed on the phone, which means subsequent text messages or tool invocations can't reference call context.
This PR:
- Adds an **`onCallEnded` lifecycle hook** to `CallManager` — fired on `call.completed`, `call.error`, and bot-initiated hangup via `endCall()`
- Adds **`SpeakOptions`** with a `recordTranscript` flag so callers can speak without polluting the transcript (useful for acknowledgement cues like "mm-hmm")
- **Exposes `resolveMainSessionKey`** in `PluginRuntime.system` so plugins can resolve the canonical main session key for event routing
- **Wires transcript bridging** in the voice-call plugin: on call end, a truncated transcript summary is injected into the main agent session via `enqueueSystemEvent`
- Adds **UI hint entries** for `responseCues.*` and `singleTopic.*` config fields
## Motivation
After a phone call ends, the assistant should know what was discussed. Currently a user could text "what did we just talk about?" and the assistant would have zero context. This bridges that gap by injecting a structured summary into the session queue.
## Changes
| File | Change |
|------|--------|
| `extensions/voice-call/src/manager/context.ts` | Add `onCallEnded` to `CallManagerHooks` |
| `extensions/voice-call/src/manager/events.ts` | Fire `onCallEnded` on `call.ended` and `call.error` |
| `extensions/voice-call/src/manager/outbound.ts` | Fire `onCallEnded` on `endCall()`, add `SpeakOptions` type |
| `extensions/voice-call/src/manager.ts` | Accept `onCallEnded` in constructor, pass through context |
| `extensions/voice-call/src/runtime.ts` | Accept and forward `onCallEnded` to `CallManager` |
| `extensions/voice-call/index.ts` | Transcript bridging consumer + config UI hints |
| `src/plugins/runtime/types.ts` | Add `resolveMainSessionKey` to `system` type |
| `src/plugins/runtime/index.ts` | Import and expose `resolveMainSessionKey` |
| `extensions/voice-call/src/manager.test.ts` | 2 new tests for `onCallEnded` callback |
## Test plan
- [x] All 55 voice-call tests pass (53 existing + 2 new)
- [x] `onCallEnded` fires on `call.completed` events
- [x] `onCallEnded` fires on bot-initiated `endCall()`
- [x] Transcript text is capped at 40 lines / 2000 chars
- [x] `SpeakOptions.recordTranscript: false` skips transcript entry
- [ ] Manual: make a call, end it, verify transcript appears in next text session
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR adds lifecycle hooks to bridge voice call transcripts into the main agent session, enabling the assistant to reference phone conversations in subsequent text interactions.
**Key changes:**
- Added `onCallEnded` lifecycle hook to `CallManager` that fires on call completion, errors, and bot-initiated hangups
- Exposed `resolveMainSessionKey` in `PluginRuntime.system` to resolve the canonical session key for event routing
- Implemented transcript bridging: on call end, a truncated transcript summary (max 40 lines / 2000 chars) is injected into the main session via `enqueueSystemEvent`
- Added `SpeakOptions` with `recordTranscript` flag to allow speaking without polluting the transcript (useful for acknowledgement cues)
- Added UI hint entries for `responseCues.*` and `singleTopic.*` config fields
- Included 2 new tests verifying `onCallEnded` fires correctly
**Implementation quality:**
- Clean hook pattern with proper error handling (try/catch around callback invocations)
- Well-tested with comprehensive unit tests
- Follows existing patterns in the codebase (system events, session key resolution)
- Hook is optional and backward-compatible
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation follows established patterns, includes comprehensive test coverage (55 tests passing, 2 new tests added), uses proper error handling throughout (try/catch blocks around all callback invocations), and is backward-compatible (hook is optional). The changes are well-scoped to the voice-call extension with a minimal, clean interface addition to the plugin runtime.
- No files require special attention
<sub>Last reviewed commit: 71449f1</sub>
<!-- greptile_other_comments_section -->
<sub>(4/5) You can add custom instructions or style guidelines for the agent [here](https://app.greptile.com/review/github)!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#22285: Voice-call: add onCallEnded lifecycle callback
by MegaPhoenix92 · 2026-02-21
82.5%
#19073: feat(voice-call): streaming TTS, barge-in, silence filler, hangup, ...
by odrobnik · 2026-02-17
78.8%
#8251: fix(voice-call): remove redundant transcript from extraSystemPrompt
by geodeterra · 2026-02-03
77.9%
#18852: fix: Voice-call state persistence is fire-and-forget, causing silen...
by coygeek · 2026-02-17
75.7%
#6702: fix(voice-call): mark calls as ended when media stream disconnects
by johngnip · 2026-02-01
74.3%
#19103: fix(voice-call): replace console.log with runtime logging
by Clawborn · 2026-02-17
73.9%
#23572: feat(voice): enable voice note conversation loop for Telegram and W...
by davidrudduck · 2026-02-22
73.9%
#16949: fix(gateway): deliver chat:final even when sessionKey is unresolved (…
by ekleziast · 2026-02-15
73.5%
#18889: feat(hooks): add agent and tool lifecycle boundaries
by vincentkoc · 2026-02-17
72.7%
#7652: fix(voice-call): fix Telnyx transcription (STT) not working
by tturnerdev · 2026-02-03
72.6%