CHANGES: Update changes log to include r403414 entry
[asterisk/asterisk.git] / apps / app_agent_pool.c
index b3f67d8..a44c75d 100644 (file)
@@ -40,13 +40,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/pbx.h"
 #include "asterisk/module.h"
 #include "asterisk/channel.h"
-#include "asterisk/bridging.h"
-#include "asterisk/bridging_basic.h"
+#include "asterisk/bridge.h"
+#include "asterisk/bridge_internal.h"
+#include "asterisk/bridge_basic.h"
+#include "asterisk/bridge_after.h"
 #include "asterisk/config_options.h"
 #include "asterisk/features_config.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/stringfields.h"
 #include "asterisk/stasis_channels.h"
+#include "asterisk/causes.h"
 
 /*** DOCUMENTATION
        <application name="AgentLogin" language="en_US">
@@ -67,9 +70,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Login an agent to the system.  Any agent authentication is assumed to
                        already be done by dialplan.  While logged in, the agent can receive calls
-                       and will hear a configurable <literal>beep</literal> sound when a new call
-                       comes in for the agent.  Login failures will continue in the dialplan
-                       with <variable>AGENT_STATUS</variable> set.</para>
+                       and will hear the sound file specified by the config option custom_beep
+                       when a new call comes in for the agent.  Login failures will continue in
+                       the dialplan with <variable>AGENT_STATUS</variable> set.</para>
                        <para>Before logging in, you can setup on the real agent channel the
                        CHANNEL(dtmf-features) an agent will have when talking to a caller
                        and you can setup on the channel running this application the
@@ -161,7 +164,69 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                <description>
                        <para>Will list info about all defined agents.</para>
                </description>
+               <see-also>
+                       <ref type="managerEvent">Agents</ref>
+                       <ref type="managerEvent">AgentsComplete</ref>
+               </see-also>
        </manager>
+       <managerEvent language="en_US" name="Agents">
+               <managerEventInstance class="EVENT_FLAG_AGENT">
+                       <synopsis>
+                               Response event in a series to the Agents AMI action containing
+                               information about a defined agent.
+                       </synopsis>
+                       <syntax>
+                               <parameter name="Agent">
+                                       <para>Agent ID of the agent.</para>
+                               </parameter>
+                               <parameter name="Name">
+                                       <para>User friendly name of the agent.</para>
+                               </parameter>
+                               <parameter name="Status">
+                                       <para>Current status of the agent.</para>
+                                       <para>The valid values are:</para>
+                                       <enumlist>
+                                               <enum name="AGENT_LOGGEDOFF" />
+                                               <enum name="AGENT_IDLE" />
+                                               <enum name="AGENT_ONCALL" />
+                                       </enumlist>
+                               </parameter>
+                               <parameter name="TalkingToChan">
+                                       <para><variable>BRIDGEPEER</variable> value on agent channel.</para>
+                                       <para>Present if Status value is <literal>AGENT_ONCALL</literal>.</para>
+                               </parameter>
+                               <parameter name="CallStarted">
+                                       <para>Epoche time when the agent started talking with the caller.</para>
+                                       <para>Present if Status value is <literal>AGENT_ONCALL</literal>.</para>
+                               </parameter>
+                               <parameter name="LoggedInTime">
+                                       <para>Epoche time when the agent logged in.</para>
+                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
+                               </parameter>
+                               <channel_snapshot/>
+                               <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       </syntax>
+                       <description>
+                               <para>The channel snapshot is present if the Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
+                       </description>
+                       <see-also>
+                               <ref type="manager">Agents</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
+       <managerEvent language="en_US" name="AgentsComplete">
+               <managerEventInstance class="EVENT_FLAG_AGENT">
+                       <synopsis>
+                               Final response event in a series of events to the Agents AMI action.
+                       </synopsis>
+                       <syntax>
+                               <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       </syntax>
+                       <see-also>
+                               <ref type="manager">Agents</ref>
+                       </see-also>
+               </managerEventInstance>
+       </managerEvent>
        <manager name="AgentLogoff" language="en_US">
                <synopsis>
                        Sets an agent as no longer logged in.
@@ -207,6 +272,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        <synopsis>DTMF key sequence the agent uses to acknowledge a call.</synopsis>
                                        <description>
                                                <note><para>The option is overridden by <variable>AGENTACCEPTDTMF</variable> on agent login.</para></note>
+                                               <note><para>The option is ignored unless the ackcall option is enabled.</para></note>
                                                <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
                                        </description>
                                </configOption>
@@ -218,6 +284,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                                logged off.  If set to zero then the call will wait forever for
                                                the agent to acknowledge.</para>
                                                <note><para>The option is overridden by <variable>AGENTAUTOLOGOFF</variable> on agent login.</para></note>
+                                               <note><para>The option is ignored unless the ackcall option is enabled.</para></note>
                                                <xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
                                        </description>
                                </configOption>
@@ -612,6 +679,8 @@ static inline void _agent_unlock(struct agent_pvt *agent, const char *file, cons
  *
  * \note Assumes the agent lock is already obtained.
  *
+ * \note Defined locking order is channel lock then agent lock.
+ *
  * \return Nothing
  */
 static struct ast_channel *agent_lock_logged(struct agent_pvt *agent)
@@ -692,7 +761,7 @@ static void agent_pvt_destructor(void *vdoomed)
 
        ast_party_connected_line_free(&doomed->waiting_colp);
        if (doomed->caller_bridge) {
-               ast_bridge_destroy(doomed->caller_bridge);
+               ast_bridge_destroy(doomed->caller_bridge, AST_CAUSE_USER_BUSY);
                doomed->caller_bridge = NULL;
        }
        if (doomed->logged) {
@@ -951,15 +1020,17 @@ static void agent_connect_caller(struct ast_bridge_channel *bridge_channel, stru
 
        if (!caller_bridge) {
                /* Reset agent. */
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
+                       AST_CAUSE_NORMAL_CLEARING);
                return;
        }
        res = ast_bridge_move(caller_bridge, bridge_channel->bridge, bridge_channel->chan,
                NULL, 0);
        if (res) {
                /* Reset agent. */
-               ast_bridge_destroy(caller_bridge);
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               ast_bridge_destroy(caller_bridge, AST_CAUSE_USER_BUSY);
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
+                       AST_CAUSE_NORMAL_CLEARING);
                return;
        }
        ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_ANSWER, NULL, 0);
@@ -973,12 +1044,11 @@ static void agent_connect_caller(struct ast_bridge_channel *bridge_channel, stru
                 * The agent is in the new bridge so we can invoke the
                 * mixmonitor hook to only start recording.
                 */
-               ast_bridge_features_do(AST_BRIDGE_BUILTIN_AUTOMIXMON, caller_bridge,
-                       bridge_channel, &options);
+               ast_bridge_features_do(AST_BRIDGE_BUILTIN_AUTOMIXMON, bridge_channel, &options);
        }
 }
 
-static int bridge_agent_hold_ack(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+static int bridge_agent_hold_ack(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
        struct agent_pvt *agent = hook_pvt;
 
@@ -996,7 +1066,7 @@ static int bridge_agent_hold_ack(struct ast_bridge *bridge, struct ast_bridge_ch
        return 0;
 }
 
-static int bridge_agent_hold_heartbeat(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+static int bridge_agent_hold_heartbeat(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
        struct agent_pvt *agent = hook_pvt;
        int probation_timedout = 0;
@@ -1055,13 +1125,15 @@ static int bridge_agent_hold_heartbeat(struct ast_bridge *bridge, struct ast_bri
 
        if (deferred_logoff) {
                ast_debug(1, "Agent %s: Deferred logoff.\n", agent->username);
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
+                       AST_CAUSE_NORMAL_CLEARING);
        } else if (probation_timedout) {
                ast_debug(1, "Agent %s: Login complete.\n", agent->username);
                agent_devstate_changed(agent->username);
        } else if (ack_timedout) {
                ast_debug(1, "Agent %s: Ack call timeout.\n", agent->username);
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
+                       AST_CAUSE_NORMAL_CLEARING);
        } else if (wrapup_timedout) {
                ast_debug(1, "Agent %s: Wrapup timeout. Ready for new call.\n", agent->username);
                agent_devstate_changed(agent->username);
@@ -1071,7 +1143,7 @@ static int bridge_agent_hold_heartbeat(struct ast_bridge *bridge, struct ast_bri
 }
 
 static void agent_after_bridge_cb(struct ast_channel *chan, void *data);
-static void agent_after_bridge_cb_failed(enum ast_after_bridge_cb_reason reason, void *data);
+static void agent_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data);
 
 /*!
  * \internal
@@ -1135,7 +1207,7 @@ static int bridge_agent_hold_push(struct ast_bridge *self, struct ast_bridge_cha
 
        /* Add heartbeat interval hook. */
        ao2_ref(agent, +1);
-       if (ast_bridge_interval_hook(bridge_channel->features, 1000,
+       if (ast_bridge_interval_hook(bridge_channel->features, 0, 1000,
                bridge_agent_hold_heartbeat, agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
                ao2_ref(agent, -1);
                res = -1;
@@ -1148,7 +1220,7 @@ static int bridge_agent_hold_push(struct ast_bridge *self, struct ast_bridge_cha
        }
 
        if (swap) {
-               res = ast_after_bridge_callback_set(chan, agent_after_bridge_cb,
+               res = ast_bridge_set_after_callback(chan, agent_after_bridge_cb,
                        agent_after_bridge_cb_failed, chan);
                if (res) {
                        ast_channel_remove_bridge_role(chan, "holding_participant");
@@ -1166,7 +1238,8 @@ static int bridge_agent_hold_push(struct ast_bridge *self, struct ast_bridge_cha
                 * agent will have some slightly different behavior in corner
                 * cases.
                 */
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
+                       AST_CAUSE_NORMAL_CLEARING);
                return 0;
        }
 
@@ -1202,7 +1275,7 @@ static int bridge_agent_hold_push(struct ast_bridge *self, struct ast_bridge_cha
                break;
        case AGENT_STATE_READY_FOR_CALL:
                /*
-                * Likely someone manally kicked us out of the holding bridge
+                * Likely someone manually kicked us out of the holding bridge
                 * and we came right back in.
                 */
                agent_unlock(agent);
@@ -1280,7 +1353,7 @@ static void bridge_agent_hold_pull(struct ast_bridge *self, struct ast_bridge_ch
  */
 static void bridge_agent_hold_dissolving(struct ast_bridge *self)
 {
-       ao2_global_obj_replace_unref(agent_holding, NULL);
+       ao2_global_obj_release(agent_holding);
        ast_bridge_base_v_table.dissolving(self);
 }
 
@@ -1290,15 +1363,16 @@ static struct ast_bridge *bridge_agent_hold_new(void)
 {
        struct ast_bridge *bridge;
 
-       bridge = ast_bridge_alloc(sizeof(struct ast_bridge), &bridge_agent_hold_v_table);
-       bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_HOLDING,
+       bridge = bridge_alloc(sizeof(struct ast_bridge), &bridge_agent_hold_v_table);
+       bridge = bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_HOLDING,
                AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
-                       | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
-       bridge = ast_bridge_register(bridge);
+                       | AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM | AST_BRIDGE_FLAG_TRANSFER_PROHIBITED,
+               "AgentPool", NULL);
+       bridge = bridge_register(bridge);
        return bridge;
 }
 
-static void bridging_init_agent_hold(void)
+static void bridge_init_agent_hold(void)
 {
        /* Setup bridge agent_hold subclass v_table. */
        bridge_agent_hold_v_table = ast_bridge_base_v_table;
@@ -1380,7 +1454,7 @@ static void agent_logout(struct agent_pvt *agent)
        logged = agent->logged;
        agent->logged = NULL;
        caller_bridge = agent->caller_bridge;
-       caller_bridge = NULL;
+       agent->caller_bridge = NULL;
        agent->state = AGENT_STATE_LOGGED_OUT;
        agent->devstate = AST_DEVICE_UNAVAILABLE;
        ast_clear_flag(agent, AST_FLAGS_ALL);
@@ -1388,10 +1462,12 @@ static void agent_logout(struct agent_pvt *agent)
        agent_devstate_changed(agent->username);
 
        if (caller_bridge) {
-               ast_bridge_destroy(caller_bridge);
+               ast_bridge_destroy(caller_bridge, AST_CAUSE_USER_BUSY);
        }
 
+       ast_channel_lock(logged);
        send_agent_logoff(logged, agent->username, time_logged_in);
+       ast_channel_unlock(logged);
        ast_verb(2, "Agent '%s' logged out.  Logged in for %ld seconds.\n",
                agent->username, time_logged_in);
        ast_channel_unref(logged);
@@ -1411,68 +1487,86 @@ static void agent_run(struct agent_pvt *agent, struct ast_channel *logged)
 {
        struct ast_bridge_features features;
 
-       if (!ast_bridge_features_init(&features)) {
-               for (;;) {
-                       struct agents_cfg *cfgs;
-                       struct agent_cfg *cfg_new;
-                       struct agent_cfg *cfg_old;
-                       struct ast_bridge *holding;
-                       struct ast_bridge *caller_bridge;
-
-                       holding = ao2_global_obj_ref(agent_holding);
-                       if (!holding) {
-                               break;
-                       }
+       if (ast_bridge_features_init(&features)) {
+               ast_channel_hangupcause_set(logged, AST_CAUSE_NORMAL_CLEARING);
+               goto agent_run_cleanup;
+       }
+       for (;;) {
+               struct agents_cfg *cfgs;
+               struct agent_cfg *cfg_new;
+               struct agent_cfg *cfg_old;
+               struct ast_bridge *holding;
+               struct ast_bridge *caller_bridge;
 
-                       ast_bridge_join(holding, logged, NULL, &features, NULL, 1);
-                       if (logged != agent->logged) {
-                               break;
-                       }
+               ast_channel_hangupcause_set(logged, AST_CAUSE_NORMAL_CLEARING);
 
-                       if (agent->dead) {
-                               break;
-                       }
+               holding = ao2_global_obj_ref(agent_holding);
+               if (!holding) {
+                       ast_debug(1, "Agent %s: Someone destroyed the agent holding bridge.\n",
+                               agent->username);
+                       break;
+               }
 
-                       /* Update the agent's config before rejoining the holding bridge. */
-                       cfgs = ao2_global_obj_ref(cfg_handle);
-                       if (!cfgs) {
-                               break;
-                       }
-                       cfg_new = ao2_find(cfgs->agents, agent->username, OBJ_KEY);
-                       ao2_ref(cfgs, -1);
-                       if (!cfg_new) {
-                               break;
-                       }
-                       agent_lock(agent);
-                       cfg_old = agent->cfg;
-                       agent->cfg = cfg_new;
+               /*
+                * When the agent channel leaves the bridging system we usually
+                * want to put the agent back into the holding bridge for the
+                * next caller.
+                */
+               ast_bridge_join(holding, logged, NULL, &features, NULL,
+                       AST_BRIDGE_JOIN_PASS_REFERENCE);
+               if (logged != agent->logged) {
+                       /* This channel is no longer the logged in agent. */
+                       break;
+               }
 
-                       agent->last_disconnect = ast_tvnow();
+               if (agent->dead) {
+                       /* The agent is no longer configured. */
+                       break;
+               }
 
-                       /* Clear out any caller bridge before rejoining the holding bridge. */
-                       caller_bridge = agent->caller_bridge;
-                       agent->caller_bridge = NULL;
-                       agent_unlock(agent);
-                       ao2_ref(cfg_old, -1);
-                       if (caller_bridge) {
-                               ast_bridge_destroy(caller_bridge);
-                       }
+               /* Update the agent's config before rejoining the holding bridge. */
+               cfgs = ao2_global_obj_ref(cfg_handle);
+               if (!cfgs) {
+                       /* There is no agent configuration.  All agents were destroyed. */
+                       break;
+               }
+               cfg_new = ao2_find(cfgs->agents, agent->username, OBJ_KEY);
+               ao2_ref(cfgs, -1);
+               if (!cfg_new) {
+                       /* The agent is no longer configured. */
+                       break;
+               }
+               agent_lock(agent);
+               cfg_old = agent->cfg;
+               agent->cfg = cfg_new;
 
-                       if (agent->state == AGENT_STATE_LOGGING_OUT
-                               || agent->deferred_logoff
-                               || ast_check_hangup_locked(logged)) {
-                               break;
-                       }
+               agent->last_disconnect = ast_tvnow();
 
-                       /*
-                        * It is safe to access agent->waiting_colp without a lock.  It
-                        * is only setup on agent login and not changed.
-                        */
-                       ast_channel_update_connected_line(logged, &agent->waiting_colp, NULL);
+               /* Clear out any caller bridge before rejoining the holding bridge. */
+               caller_bridge = agent->caller_bridge;
+               agent->caller_bridge = NULL;
+               agent_unlock(agent);
+               ao2_ref(cfg_old, -1);
+               if (caller_bridge) {
+                       ast_bridge_destroy(caller_bridge, AST_CAUSE_USER_BUSY);
                }
-               ast_bridge_features_cleanup(&features);
+
+               if (agent->state == AGENT_STATE_LOGGING_OUT
+                       || agent->deferred_logoff
+                       || ast_check_hangup_locked(logged)) {
+                       /* The agent was requested to logout or hungup. */
+                       break;
+               }
+
+               /*
+                * It is safe to access agent->waiting_colp without a lock.  It
+                * is only setup on agent login and not changed.
+                */
+               ast_channel_update_connected_line(logged, &agent->waiting_colp, NULL);
        }
+       ast_bridge_features_cleanup(&features);
 
+agent_run_cleanup:
        agent_lock(agent);
        if (logged != agent->logged) {
                /*
@@ -1502,7 +1596,7 @@ static void agent_after_bridge_cb(struct ast_channel *chan, void *data)
        ao2_ref(agent, -1);
 }
 
-static void agent_after_bridge_cb_failed(enum ast_after_bridge_cb_reason reason, void *data)
+static void agent_after_bridge_cb_failed(enum ast_bridge_after_cb_reason reason, void *data)
 {
        struct ast_channel *chan = data;
        struct agent_pvt *agent;
@@ -1513,7 +1607,7 @@ static void agent_after_bridge_cb_failed(enum ast_after_bridge_cb_reason reason,
        }
        ast_log(LOG_WARNING, "Agent %s: Forced logout.  Lost control of %s because: %s\n",
                agent->username, ast_channel_name(chan),
-               ast_after_bridge_cb_reason_string(reason));
+               ast_bridge_after_cb_reason_string(reason));
        agent_lock(agent);
        agent_logout(agent);
        ao2_ref(agent, -1);
@@ -1524,7 +1618,7 @@ static void agent_after_bridge_cb_failed(enum ast_after_bridge_cb_reason reason,
  * \brief Get the lock on the agent bridge_channel and return it.
  * \since 12.0.0
  *
- * \param agent Whose bridge_chanel to get.
+ * \param agent Whose bridge_channel to get.
  *
  * \retval bridge_channel on success (Reffed and locked).
  * \retval NULL on error.
@@ -1580,23 +1674,25 @@ static void caller_abort_agent(struct agent_pvt *agent)
                agent->caller_bridge = NULL;
                agent_unlock(agent);
                if (caller_bridge) {
-                       ast_bridge_destroy(caller_bridge);
+                       ast_bridge_destroy(caller_bridge, AST_CAUSE_USER_BUSY);
                }
                return;
        }
 
        /* Kick the agent out of the holding bridge to reset it. */
-       ast_bridge_change_state_nolock(logged, AST_BRIDGE_CHANNEL_STATE_END);
+       ast_bridge_channel_leave_bridge_nolock(logged, BRIDGE_CHANNEL_STATE_END,
+               AST_CAUSE_NORMAL_CLEARING);
        ast_bridge_channel_unlock(logged);
 }
 
-static int caller_safety_timeout(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+static int caller_safety_timeout(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
        struct agent_pvt *agent = hook_pvt;
 
        if (agent->state == AGENT_STATE_CALL_PRESENT) {
                ast_verb(3, "Agent '%s' did not respond.  Safety timeout.\n", agent->username);
-               ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
+                       AST_CAUSE_USER_BUSY);
                caller_abort_agent(agent);
        }
 
@@ -1615,6 +1711,13 @@ static void agent_alert(struct ast_bridge_channel *bridge_channel, const void *p
                return;
        }
 
+       /* Change holding bridge participant role's idle mode to silence */
+       ast_bridge_channel_lock_bridge(bridge_channel);
+       ast_bridge_channel_clear_roles(bridge_channel);
+       ast_channel_set_bridge_role_option(bridge_channel->chan, "holding_participant", "idle_mode", "silence");
+       ast_bridge_channel_establish_roles(bridge_channel);
+       ast_bridge_unlock(bridge_channel->bridge);
+
        /* Alert the agent. */
        agent_lock(agent);
        playfile = ast_strdupa(agent->cfg->beep_sound);
@@ -1643,8 +1746,8 @@ static void agent_alert(struct ast_bridge_channel *bridge_channel, const void *p
 
 static int send_alert_to_agent(struct ast_bridge_channel *bridge_channel, const char *agent_id)
 {
-       return ast_bridge_channel_queue_callback(bridge_channel, agent_alert, agent_id,
-               strlen(agent_id) + 1);
+       return ast_bridge_channel_queue_callback(bridge_channel,
+               AST_BRIDGE_CHANNEL_CB_OPTION_MEDIA, agent_alert, agent_id, strlen(agent_id) + 1);
 }
 
 static int send_colp_to_agent(struct ast_bridge_channel *bridge_channel, struct ast_party_connected_line *connected)
@@ -1694,7 +1797,7 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
                return -1;
        }
 
-       parse = ast_strdupa(data ?: "");
+       parse = ast_strdupa(data);
        AST_STANDARD_APP_ARGS(args, parse);
 
        if (ast_strlen_zero(args.agent_id)) {
@@ -1716,7 +1819,7 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
 
        /* Add safety timeout hook. */
        ao2_ref(agent, +1);
-       if (ast_bridge_interval_hook(&caller_features, CALLER_SAFETY_TIMEOUT_TIME,
+       if (ast_bridge_interval_hook(&caller_features, 0, CALLER_SAFETY_TIMEOUT_TIME,
                caller_safety_timeout, agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) {
                ao2_ref(agent, -1);
                ast_bridge_features_cleanup(&caller_features);
@@ -1741,7 +1844,7 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
        case AGENT_STATE_LOGGING_OUT:
                agent_unlock(agent);
                ast_party_connected_line_free(&connected);
-               ast_bridge_destroy(caller_bridge);
+               ast_bridge_destroy(caller_bridge, 0);
                ast_bridge_features_cleanup(&caller_features);
                ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
                pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_LOGGED_IN");
@@ -1755,7 +1858,7 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
        default:
                agent_unlock(agent);
                ast_party_connected_line_free(&connected);
-               ast_bridge_destroy(caller_bridge);
+               ast_bridge_destroy(caller_bridge, 0);
                ast_bridge_features_cleanup(&caller_features);
                ast_verb(3, "Agent '%s' is busy.\n", agent->username);
                pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "BUSY");
@@ -1767,7 +1870,7 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
        logged = agent_bridge_channel_get_lock(agent);
        if (!logged) {
                ast_party_connected_line_free(&connected);
-               ast_bridge_destroy(caller_bridge);
+               ast_bridge_destroy(caller_bridge, 0);
                ast_bridge_features_cleanup(&caller_features);
                ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
                pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_LOGGED_IN");
@@ -1782,7 +1885,7 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
        ast_bridge_channel_unlock(logged);
        ao2_ref(logged, -1);
        if (res) {
-               ast_bridge_destroy(caller_bridge);
+               ast_bridge_destroy(caller_bridge, 0);
                ast_bridge_features_cleanup(&caller_features);
                ast_verb(3, "Agent '%s': Failed to alert the agent.\n", agent->username);
                pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "ERROR");
@@ -1791,7 +1894,8 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
        }
 
        ast_indicate(chan, AST_CONTROL_RINGING);
-       ast_bridge_join(caller_bridge, chan, NULL, &caller_features, NULL, 1);
+       ast_bridge_join(caller_bridge, chan, NULL, &caller_features, NULL,
+               AST_BRIDGE_JOIN_PASS_REFERENCE);
        ast_bridge_features_cleanup(&caller_features);
 
        return -1;
@@ -1899,7 +2003,7 @@ static int agent_login_exec(struct ast_channel *chan, const char *data)
                return -1;
        }
 
-       parse = ast_strdupa(data ?: "");
+       parse = ast_strdupa(data);
        AST_STANDARD_APP_ARGS(args, parse);
 
        if (ast_strlen_zero(args.agent_id)) {
@@ -1931,6 +2035,7 @@ static int agent_login_exec(struct ast_channel *chan, const char *data)
        agent->logged = ast_channel_ref(chan);
        agent->last_disconnect = ast_tvnow();
        time(&agent->login_start);
+       agent->deferred_logoff = 0;
        agent_unlock(agent);
 
        agent_login_channel_config(agent, chan);
@@ -1943,7 +2048,9 @@ static int agent_login_exec(struct ast_channel *chan, const char *data)
        ast_verb(2, "Agent '%s' logged in (format %s/%s)\n", agent->username,
                ast_getformatname(ast_channel_readformat(chan)),
                ast_getformatname(ast_channel_writeformat(chan)));
+       ast_channel_lock(chan);
        send_agent_login(chan, agent->username);
+       ast_channel_unlock(chan);
 
        agent_run(agent, chan);
        return -1;
@@ -2309,13 +2416,7 @@ static int action_agents(struct mansession *s, const struct message *m)
 
        iter = ao2_iterator_init(agents, 0);
        for (; (agent = ao2_iterator_next(&iter)); ao2_ref(agent, -1)) {
-               struct ast_party_id party_id;
                struct ast_channel *logged;
-               const char *login_chan;
-               const char *talking_to;
-               const char *talking_to_chan;
-               const char *status;
-               time_t login_start;
 
                agent_lock(agent);
                logged = agent_lock_logged(agent);
@@ -2327,40 +2428,40 @@ static int action_agents(struct mansession *s, const struct message *m)
                 * AGENT_ONCALL    - Agent is logged in, and on a call
                 * AGENT_UNKNOWN   - Don't know anything about agent. Shouldn't ever get this.
                 */
+               ast_str_set(&out, 0, "Agent: %s\r\n", agent->username);
+               ast_str_append(&out, 0, "Name: %s\r\n", agent->cfg->full_name);
 
                if (logged) {
-                       login_chan = ast_channel_name(logged);
-                       login_start = agent->login_start;
+                       const char *talking_to_chan;
+                       struct ast_str *logged_headers;
+                       RAII_VAR(struct ast_channel_snapshot *, logged_snapshot, ast_channel_snapshot_create(logged), ao2_cleanup);
+
+                       if (!logged_snapshot
+                               || !(logged_headers =
+                                        ast_manager_build_channel_state_string(logged_snapshot))) {
+                               ast_channel_unlock(logged);
+                               ast_channel_unref(logged);
+                               agent_unlock(agent);
+                               continue;
+                       }
+
                        talking_to_chan = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
                        if (!ast_strlen_zero(talking_to_chan)) {
-                               party_id = ast_channel_connected_effective_id(logged);
-                               talking_to = S_COR(party_id.number.valid, party_id.number.str, "n/a");
-                               status = "AGENT_ONCALL";
+                               ast_str_append(&out, 0, "Status: %s\r\n", "AGENT_ONCALL");
+                               ast_str_append(&out, 0, "TalkingToChan: %s\r\n", talking_to_chan);
+                               ast_str_append(&out, 0, "CallStarted: %ld\n", (long) agent->call_start);
                        } else {
-                               talking_to = "n/a";
-                               talking_to_chan = "n/a";
-                               status = "AGENT_IDLE";
+                               ast_str_append(&out, 0, "Status: %s\r\n", "AGENT_IDLE");
                        }
-               } else {
-                       login_chan = "n/a";
-                       login_start = 0;
-                       talking_to = "n/a";
-                       talking_to_chan = "n/a";
-                       status = "AGENT_LOGGEDOFF";
-               }
-
-               ast_str_set(&out, 0, "Agent: %s\r\n", agent->username);
-               ast_str_append(&out, 0, "Name: %s\r\n", S_OR(agent->cfg->full_name, "None"));
-               ast_str_append(&out, 0, "Status: %s\r\n", status);
-               ast_str_append(&out, 0, "LoggedInChan: %s\r\n", login_chan);
-               ast_str_append(&out, 0, "LoggedInTime: %ld\r\n", (long) login_start);
-               ast_str_append(&out, 0, "TalkingTo: %s\r\n", talking_to);
-               ast_str_append(&out, 0, "TalkingToChan: %s\r\n", talking_to_chan);
-
-               if (logged) {
+                       ast_str_append(&out, 0, "LoggedInTime: %ld\r\n", (long) agent->login_start);
+                       ast_str_append(&out, 0, "%s", ast_str_buffer(logged_headers));
                        ast_channel_unlock(logged);
                        ast_channel_unref(logged);
+                       ast_free(logged_headers);
+               } else {
+                       ast_str_append(&out, 0, "Status: %s\r\n", "AGENT_LOGGEDOFF");
                }
+
                agent_unlock(agent);
 
                astman_append(s, "Event: Agents\r\n"
@@ -2417,7 +2518,7 @@ static int unload_module(void)
        /* Destroy agent holding bridge. */
        holding = ao2_global_obj_replace(agent_holding, NULL);
        if (holding) {
-               ast_bridge_destroy(holding);
+               ast_bridge_destroy(holding, 0);
        }
 
        destroy_config();
@@ -2443,7 +2544,7 @@ static int load_module(void)
        }
 
        /* Init agent holding bridge v_table. */
-       bridging_init_agent_hold();
+       bridge_init_agent_hold();
 
        /* Setup to provide Agent:agent-id device state. */
        res |= ast_devstate_prov_add("Agent", agent_pvt_devstate_get);