#22285: Voice-call: add onCallEnded lifecycle callback
channel: voice-call
size: S
Cluster:
Voice Call Enhancements and Fixes
## Summary
Adds an optional `onCallEnded` lifecycle callback to the voice-call extension. The callback fires once when a call terminates through any path:
- Provider `call.ended` event (normal completion, remote hangup)
- Non-retryable `call.error` event
- Bot-initiated `endCall()` hangup
The callback is wrapped in try/catch so a misbehaving consumer cannot break the call teardown flow.
This enables extensions to react to call completion — for example, bridging call transcripts into the main agent session, logging call metrics, or triggering follow-up workflows.
**Changes:**
- `context.ts` — add `onCallEnded` to `CallManagerHooks` type
- `manager.ts` — accept callback in constructor, thread to context
- `events.ts` — fire on `call.ended` and non-retryable `call.error`
- `outbound.ts` — fire on bot hangup via `endCall()`
- `runtime.ts` — thread `onCallEnded` param through `createVoiceCallRuntime`
- `manager.test.ts` — 5 new tests covering all end paths + error resilience
## Test plan
- [x] `pnpm build` passes
- [x] `pnpm vitest --run extensions/voice-call/src/manager.test.ts` — 16 tests pass (11 existing + 5 new)
- [x] Format check passes (`pnpm format:check`)
- [x] No new TS errors (only pre-existing discord `send.components.test.ts`)
- [x] Callback fires on `call.ended`, `endCall()`, and non-retryable `call.error`
- [x] Callback does NOT fire on retryable `call.error`
- [x] Throwing callback does not break call teardown
🤖 AI-assisted PR (reviewed and tested manually)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Added optional `onCallEnded` lifecycle callback to voice-call extension. The callback fires when a call terminates through normal completion, non-retryable errors, or bot-initiated hangup. Implementation follows the existing `onCallAnswered` hook pattern with try/catch protection to prevent callback errors from breaking call teardown.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation is clean, well-tested (5 new tests covering all paths), follows existing architectural patterns (`onCallAnswered` hook), and includes proper error handling with try/catch blocks to prevent callback errors from breaking teardown. The changes are backward-compatible (optional parameter) and touch only the necessary files.
- No files require special attention
<sub>Last reviewed commit: 7e1b38a</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21566: feat(voice-call): bridge call transcripts to main agent session
by MegaPhoenix92 · 2026-02-20
82.5%
#6702: fix(voice-call): mark calls as ended when media stream disconnects
by johngnip · 2026-02-01
76.8%
#4325: fix(voice-call): verify call status with provider before loading st...
by garnetlyx · 2026-01-30
73.0%
#18852: fix: Voice-call state persistence is fire-and-forget, causing silen...
by coygeek · 2026-02-17
72.9%
#19103: fix(voice-call): replace console.log with runtime logging
by Clawborn · 2026-02-17
69.6%
#19073: feat(voice-call): streaming TTS, barge-in, silence filler, hangup, ...
by odrobnik · 2026-02-17
68.9%
#9760: fix(voice-call): discard stale calls on plugin restart
by leszekszpunar · 2026-02-05
68.6%
#18889: feat(hooks): add agent and tool lifecycle boundaries
by vincentkoc · 2026-02-17
66.1%
#21532: Security/Voice Call: block signed webhook replay
by bmendonca3 · 2026-02-20
65.8%
#12597: voice-call: add Asterisk ARI provider + core STT
by w0s1nsk1 · 2026-02-09
65.6%