Applications currently have no way to observe a mid-call hold/un-hold.
The feature-server handles the in-dialog re-INVITE internally (re-modifies
the endpoint, returns 200) but never notifies the app, so a transcription
or agent-assist app can't pause/resume when a caller is held or retrieved.
This detects the hold state transition on a re-INVITE and delivers it over
the existing sipRequestWithinDialogHook channel as a verb:hook, plus emits
a local 'hold'/'unhold' event so in-process tasks (e.g. transcribe) can react.
- call-session.js: ordinary calls — reuse isOnhold() (a=sendonly/a=inactive).
- siprec-call-session.js: SIPREC overrides _onReinvite, so a separate handler
is required. SIPREC recording streams are sendonly by nature, so a stream
flipping to a=inactive is treated as the hold signal; the full multipart
body (SDP + rs-metadata) is always forwarded. Fires on transition only;
other re-INVITEs are delivered as event:"reinvite".
Delivery uses requestor.request('verb:hook', ...) — the same channel
_handleRefer uses — rather than a task-bound performHook, so events are not
dropped if the current task has ended. Enable per call with:
{"verb":"config","sipRequestWithinDialogHook":"/hold-events"}
Confirmed live against Cisco CUBE (Cisco-SIPGateway/IOS-17.12.7b) over SIPREC:
both recording streams flip sendonly<->inactive on hold/resume, each
transition emitted correctly.
The whole feature is opt-in behind the JAMBONES_HOLD_UNHOLD_EVENTS env var;
when it is unset, _notifyHoldState / _notifySipRecReinvite are no-ops (neither
the local event nor the hook fires).
The optionalDependencies pinned utf-8-validate ^6.0.3, but ws@8 only
accepts ^5.0.2 as its optional peer and ibm-watson's websocket dep
hard-requires ^5.0.2. That 5.x-vs-6.x split forced a dual-version tree
that npm 10 and npm 11 lay out differently, so a lockfile generated with
npm 11 (local) failed 'npm ci' under npm 10 (CI, node 20) with
'Missing: utf-8-validate@5.0.10 from lock file'. The 6.x pin never
accelerated ws anyway. Removing it collapses to a single utf-8-validate
5.0.10 that both npm majors resolve identically.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* add null check for eventHook
* move guard to superclass, remove logging that adds no value
---------
Co-authored-by: rhonda hollis <rhonda@jambonz.org>
Co-authored-by: Dave Horton <daveh@beachdognet.com>
Corrects the parameter passed to _lccBoostAudioSignal to use
opts.boostAudioSignal instead of the entire opts object, ensuring
the boostAudioSignal option works correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: propagate SIP 488 error to SBC on endpoint allocation failure
When FreeSWITCH returns a SIP 488 'Not Acceptable Here' error during
endpoint allocation (e.g., codec incompatibility), this error was not
being propagated back to the SBC/client. Instead, the call would wait
indefinitely for websocket commands or return a generic 603 response.
Implementation:
- In _evalEndpointPrecondition(), detect SipError by checking
err.type === 'SipError' or err.name === 'SipError'
- Extract the SIP status code (e.g., 488), reason, and the Reason
header from the error response (e.g., Q.850;cause=88;text=INCOMPATIBLE_DESTINATION)
- Send the SIP error response immediately to the SBC with:
- X-Reason header: endpoint allocation failure details
- Reason header: original Q.850 cause from FreeSWITCH
- Notify call status change as Failed with proper SIP status
- Release the call immediately instead of waiting for commands
Also added fallback handling in InboundCallSession._onTasksDone() to
propagate the stored error if immediate send was not possible.
* wip
* Simplify SipError check to only use err.name
* refactor recording
removed the test of `(this.cs.accountInfo.account.record_all_calls || this.cs.application.record_all_calls` from backround-task-manager.jsL138 as this check is already done in call-session.js at Line 3007, also allows us to start the record from update or config verbs
* handle start recording for a call that is not yet answered
* return false if not changing recording state
* different check for status
* set hasRecording flag on callInfo when starting
* update redis on recording start
* lint
* update dependency