app_queue: Added initialization for "context" parameter
[asterisk/asterisk.git] / apps / app_agent_pool.c
index 92209e1..68bcfde 100644 (file)
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+ASTERISK_REGISTER_FILE()
 
 #include "asterisk/cli.h"
 #include "asterisk/app.h"
 #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">
@@ -105,13 +108,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="AgentId" required="true" />
                </syntax>
                <description>
-                       <para>Request an agent to connect with the channel.  Failure to find and
-                       alert an agent will continue in the dialplan with <variable>AGENT_STATUS</variable> set.</para>
+                       <para>Request an agent to connect with the channel.  Failure to find,
+                       alert the agent, or acknowledge the call will continue in the dialplan
+                       with <variable>AGENT_STATUS</variable> set.</para>
                        <para><variable>AGENT_STATUS</variable> enumeration values:</para>
                        <enumlist>
                                <enum name = "INVALID"><para>The specified agent is invalid.</para></enum>
                                <enum name = "NOT_LOGGED_IN"><para>The agent is not available.</para></enum>
                                <enum name = "BUSY"><para>The agent is on another call.</para></enum>
+                               <enum name = "NOT_CONNECTED"><para>The agent did not connect with the
+                               call.  The agent most likely did not acknowledge the call.</para></enum>
                                <enum name = "ERROR"><para>Alerting the agent failed.</para></enum>
                        </enumlist>
                </description>
@@ -200,49 +206,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        <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>
-                               <parameter name="Channel">
-                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter[@name='Channel']/para)" />
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="ChannelState">
-                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter[@name='ChannelState']/para)" />
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="ChannelStateDesc">
-                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter[@name='ChannelStateDesc']/para)" />
-                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter[@name='ChannelStateDesc']/enumlist)" />
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="CallerIDNum">
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="CallerIDName">
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="ConnectedLineNum">
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="ConnectedLineName">
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="AccountCode">
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="Context">
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="Exten">
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="Priority">
-                                       <para>Present if Status value is <literal>AGENT_IDLE</literal> or <literal>AGENT_ONCALL</literal>.</para>
-                               </parameter>
-                               <parameter name="Uniqueid">
-                                       <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter[@name='Uniqueid']/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>
@@ -579,18 +548,14 @@ static int load_config(void)
        aco_option_register(&cfg_info, "wrapuptime", ACO_EXACT, agent_types, "0", OPT_UINT_T, 0, FLDSET(struct agent_cfg, wrapup_time));
        aco_option_register(&cfg_info, "musiconhold", ACO_EXACT, agent_types, "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, moh));
        aco_option_register(&cfg_info, "recordagentcalls", ACO_EXACT, agent_types, "no", OPT_BOOL_T, 1, FLDSET(struct agent_cfg, record_agent_calls));
-       aco_option_register(&cfg_info, "custom_beep", ACO_EXACT, agent_types, "beep", OPT_STRINGFIELD_T, 1, STRFLDSET(struct agent_cfg, beep_sound));
+       aco_option_register(&cfg_info, "custom_beep", ACO_EXACT, agent_types, "beep", OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, beep_sound));
        aco_option_register(&cfg_info, "fullname", ACO_EXACT, agent_types, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, full_name));
 
        if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
-               goto error;
+               return -1;
        }
 
        return 0;
-
-error:
-       destroy_config();
-       return -1;
 }
 
 enum agent_state {
@@ -761,12 +726,17 @@ static struct ast_channel *agent_lock_logged(struct agent_pvt *agent)
  */
 static enum ast_device_state agent_pvt_devstate_get(const char *agent_id)
 {
-       RAII_VAR(struct agent_pvt *, agent, ao2_find(agents, agent_id, OBJ_KEY), ao2_cleanup);
+       enum ast_device_state dev_state = AST_DEVICE_INVALID;
+       struct agent_pvt *agent;
 
+       agent = ao2_find(agents, agent_id, OBJ_KEY);
        if (agent) {
-               return agent->devstate;
+               agent_lock(agent);
+               dev_state = agent->devstate;
+               agent_unlock(agent);
+               ao2_ref(agent, -1);
        }
-       return AST_DEVICE_INVALID;
+       return dev_state;
 }
 
 /*!
@@ -795,7 +765,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, 0);
                doomed->caller_bridge = NULL;
        }
        if (doomed->logged) {
@@ -1029,6 +999,23 @@ AST_MUTEX_DEFINE_STATIC(agent_holding_lock);
 
 /*!
  * \internal
+ * \brief Callback to clear AGENT_STATUS on the caller channel.
+ *
+ * \param bridge_channel Which channel to operate on.
+ * \param payload Data to pass to the callback. (NULL if none).
+ * \param payload_size Size of the payload if payload is non-NULL.  A number otherwise.
+ *
+ * \note The payload MUST NOT have any resources that need to be freed.
+ *
+ * \return Nothing
+ */
+static void clear_agent_status(struct ast_bridge_channel *bridge_channel, const void *payload, size_t payload_size)
+{
+       pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", NULL);
+}
+
+/*!
+ * \internal
  * \brief Connect the agent with the waiting caller.
  * \since 12.0.0
  *
@@ -1054,18 +1041,26 @@ 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, 0);
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
+                       AST_CAUSE_NORMAL_CLEARING);
+               return;
+       }
+       res = ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_ANSWER, NULL, 0)
+               || ast_bridge_channel_write_callback(bridge_channel, 0, clear_agent_status, NULL, 0);
+       if (res) {
+               /* Reset agent. */
+               ast_bridge_destroy(caller_bridge, 0);
                return;
        }
-       ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_ANSWER, NULL, 0);
 
        if (record_agent_calls) {
                struct ast_bridge_features_automixmonitor options = {
@@ -1076,12 +1071,13 @@ 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);
        }
+
+       ao2_t_ref(caller_bridge, -1, "Agent successfully in caller_bridge");
 }
 
-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;
 
@@ -1099,7 +1095,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;
@@ -1158,13 +1154,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);
@@ -1174,7 +1172,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
@@ -1238,7 +1236,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;
@@ -1251,7 +1249,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");
@@ -1269,7 +1267,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;
        }
 
@@ -1383,7 +1382,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);
 }
 
@@ -1393,15 +1392,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, 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;
@@ -1443,7 +1443,7 @@ static void send_agent_login(struct ast_channel *chan, const char *agent)
                return;
        }
 
-       ast_channel_publish_blob(chan, ast_channel_agent_login_type(), blob);
+       ast_channel_publish_cached_blob(chan, ast_channel_agent_login_type(), blob);
 }
 
 static void send_agent_logoff(struct ast_channel *chan, const char *agent, long logintime)
@@ -1459,7 +1459,7 @@ static void send_agent_logoff(struct ast_channel *chan, const char *agent, long
                return;
        }
 
-       ast_channel_publish_blob(chan, ast_channel_agent_logoff_type(), blob);
+       ast_channel_publish_cached_blob(chan, ast_channel_agent_logoff_type(), blob);
 }
 
 /*!
@@ -1491,10 +1491,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, 0);
        }
 
+       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);
@@ -1515,6 +1517,7 @@ static void agent_run(struct agent_pvt *agent, struct ast_channel *logged)
        struct ast_bridge_features features;
 
        if (ast_bridge_features_init(&features)) {
+               ast_channel_hangupcause_set(logged, AST_CAUSE_NORMAL_CLEARING);
                goto agent_run_cleanup;
        }
        for (;;) {
@@ -1524,6 +1527,8 @@ static void agent_run(struct agent_pvt *agent, struct ast_channel *logged)
                struct ast_bridge *holding;
                struct ast_bridge *caller_bridge;
 
+               ast_channel_hangupcause_set(logged, AST_CAUSE_NORMAL_CLEARING);
+
                holding = ao2_global_obj_ref(agent_holding);
                if (!holding) {
                        ast_debug(1, "Agent %s: Someone destroyed the agent holding bridge.\n",
@@ -1536,7 +1541,8 @@ static void agent_run(struct agent_pvt *agent, struct ast_channel *logged)
                 * want to put the agent back into the holding bridge for the
                 * next caller.
                 */
-               ast_bridge_join(holding, logged, NULL, &features, NULL, 1);
+               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;
@@ -1571,7 +1577,7 @@ static void agent_run(struct agent_pvt *agent, struct ast_channel *logged)
                agent_unlock(agent);
                ao2_ref(cfg_old, -1);
                if (caller_bridge) {
-                       ast_bridge_destroy(caller_bridge);
+                       ast_bridge_destroy(caller_bridge, 0);
                }
 
                if (agent->state == AGENT_STATE_LOGGING_OUT
@@ -1619,7 +1625,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;
@@ -1630,7 +1636,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);
@@ -1697,23 +1703,27 @@ 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, 0);
                }
                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_log(LOG_WARNING, "Agent '%s' process did not respond.  Safety timeout.\n",
+                       agent->username);
+               pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", "ERROR");
+
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0);
                caller_abort_agent(agent);
        }
 
@@ -1724,7 +1734,10 @@ static void agent_alert(struct ast_bridge_channel *bridge_channel, const void *p
 {
        const char *agent_id = payload;
        const char *playfile;
-       RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
+       const char *dtmf_accept;
+       struct agent_pvt *agent;
+       int digit;
+       char dtmf[2];
 
        agent = ao2_find(agents, agent_id, OBJ_KEY);
        if (!agent) {
@@ -1732,36 +1745,69 @@ static void agent_alert(struct ast_bridge_channel *bridge_channel, const void *p
                return;
        }
 
-       /* Alert the agent. */
+       /* 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);
+
        agent_lock(agent);
        playfile = ast_strdupa(agent->cfg->beep_sound);
+
+       /* Determine which DTMF digits interrupt the alerting signal. */
+       if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL)
+               ? agent->override_ack_call : agent->cfg->ack_call) {
+               dtmf_accept = ast_test_flag(agent, AGENT_FLAG_DTMF_ACCEPT)
+                       ? agent->override_dtmf_accept : agent->cfg->dtmf_accept;
+
+               /* Only the first digit of the ack will stop playback. */
+               dtmf[0] = *dtmf_accept;
+               dtmf[1] = '\0';
+               dtmf_accept = dtmf;
+       } else {
+               dtmf_accept = NULL;
+       }
        agent_unlock(agent);
-       ast_stream_and_wait(bridge_channel->chan, playfile, AST_DIGIT_NONE);
+
+       /* Alert the agent. */
+       digit = ast_stream_and_wait(bridge_channel->chan, playfile,
+               ast_strlen_zero(dtmf_accept) ? AST_DIGIT_ANY : dtmf_accept);
+       ast_stopstream(bridge_channel->chan);
 
        agent_lock(agent);
        switch (agent->state) {
        case AGENT_STATE_CALL_PRESENT:
-               if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL)
-                       ? agent->override_ack_call : agent->cfg->ack_call) {
+               if (!ast_strlen_zero(dtmf_accept)) {
                        agent->state = AGENT_STATE_CALL_WAIT_ACK;
                        agent->ack_time = ast_tvnow();
+
+                       if (0 < digit) {
+                               /* Playback was interrupted by a digit. */
+                               agent_unlock(agent);
+                               ao2_ref(agent, -1);
+                               ast_bridge_channel_feature_digit(bridge_channel, digit);
+                               return;
+                       }
                        break;
                }
 
                /* Connect to caller now. */
                ast_debug(1, "Agent %s: Immediately connecting to call.\n", agent->username);
                agent_connect_caller(bridge_channel, agent);/* Will unlock agent. */
+               ao2_ref(agent, -1);
                return;
        default:
                break;
        }
        agent_unlock(agent);
+       ao2_ref(agent, -1);
 }
 
 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)
@@ -1784,6 +1830,49 @@ static int send_colp_to_agent(struct ast_bridge_channel *bridge_channel, struct
 }
 
 /*!
+ * \internal
+ * \brief Caller joined the bridge event callback.
+ *
+ * \param bridge_channel Channel executing the feature
+ * \param hook_pvt Private data passed in when the hook was created
+ *
+ * \retval 0 Keep the callback hook.
+ * \retval -1 Remove the callback hook.
+ */
+static int caller_joined_bridge(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+       struct agent_pvt *agent = hook_pvt;
+       struct ast_bridge_channel *logged;
+       int res;
+
+       logged = agent_bridge_channel_get_lock(agent);
+       if (!logged) {
+               ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
+               pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", "NOT_LOGGED_IN");
+
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0);
+               caller_abort_agent(agent);
+               return -1;
+       }
+
+       res = send_alert_to_agent(logged, agent->username);
+       ast_bridge_channel_unlock(logged);
+       ao2_ref(logged, -1);
+       if (res) {
+               ast_verb(3, "Agent '%s': Failed to alert the agent.\n", agent->username);
+               pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", "ERROR");
+
+               ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0);
+               caller_abort_agent(agent);
+               return -1;
+       }
+
+       pbx_builtin_setvar_helper(bridge_channel->chan, "AGENT_STATUS", "NOT_CONNECTED");
+       ast_indicate(bridge_channel->chan, AST_CONTROL_RINGING);
+       return -1;
+}
+
+/*!
  * \brief Dialplan AgentRequest application to locate an agent to talk with.
  *
  * \param chan Channel wanting to talk with an agent.
@@ -1833,32 +1922,34 @@ 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);
                return -1;
        }
 
+       /* Setup the alert agent on caller joining the bridge hook. */
+       ao2_ref(agent, +1);
+       if (ast_bridge_join_hook(&caller_features, caller_joined_bridge, agent,
+               __ao2_cleanup, 0)) {
+               ao2_ref(agent, -1);
+               ast_bridge_features_cleanup(&caller_features);
+               return -1;
+       }
+
        caller_bridge = ast_bridge_basic_new();
        if (!caller_bridge) {
                ast_bridge_features_cleanup(&caller_features);
                return -1;
        }
 
-       /* Get COLP for agent. */
-       ast_party_connected_line_init(&connected);
-       ast_channel_lock(chan);
-       ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan));
-       ast_channel_unlock(chan);
-
        agent_lock(agent);
        switch (agent->state) {
        case AGENT_STATE_LOGGED_OUT:
        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");
@@ -1871,8 +1962,7 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
                break;
        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");
@@ -1881,37 +1971,55 @@ static int agent_request_exec(struct ast_channel *chan, const char *data)
        agent_unlock(agent);
        agent_devstate_changed(agent->username);
 
+       /* Get COLP for agent. */
+       ast_party_connected_line_init(&connected);
+       ast_channel_lock(chan);
+       ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan));
+       ast_channel_unlock(chan);
+
        logged = agent_bridge_channel_get_lock(agent);
        if (!logged) {
                ast_party_connected_line_free(&connected);
-               ast_bridge_destroy(caller_bridge);
+               caller_abort_agent(agent);
+               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");
-               caller_abort_agent(agent);
                return 0;
        }
 
        send_colp_to_agent(logged, &connected);
-       ast_party_connected_line_free(&connected);
-
-       res = send_alert_to_agent(logged, agent->username);
        ast_bridge_channel_unlock(logged);
        ao2_ref(logged, -1);
-       if (res) {
-               ast_bridge_destroy(caller_bridge);
-               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");
+       ast_party_connected_line_free(&connected);
+
+       if (ast_bridge_join(caller_bridge, chan, NULL, &caller_features, NULL,
+               AST_BRIDGE_JOIN_PASS_REFERENCE)) {
                caller_abort_agent(agent);
-               return 0;
+               ast_verb(3, "Agent '%s': Caller %s failed to join the bridge.\n",
+                       agent->username, ast_channel_name(chan));
+               pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "ERROR");
        }
-
-       ast_indicate(chan, AST_CONTROL_RINGING);
-       ast_bridge_join(caller_bridge, chan, NULL, &caller_features, NULL, 1);
        ast_bridge_features_cleanup(&caller_features);
 
-       return -1;
+       /* Determine if we need to continue in the dialplan after the bridge. */
+       ast_channel_lock(chan);
+       if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
+               /*
+                * The bridge was broken for a hangup that isn't real.
+                * Don't run the h extension, because the channel isn't
+                * really hung up.  This should really only happen with
+                * AST_SOFTHANGUP_ASYNCGOTO.
+                */
+               res = 0;
+       } else {
+               res = ast_check_hangup(chan)
+                       || ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
+                       || ast_strlen_zero(pbx_builtin_getvar_helper(chan, "AGENT_STATUS"));
+       }
+       ast_channel_unlock(chan);
+
+       return res ? -1 : 0;
 }
 
 /*!
@@ -2048,19 +2156,21 @@ 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);
 
-       if (!ast_test_flag(&opts, OPT_SILENT)
-               && !ast_streamfile(chan, "agent-loginok", ast_channel_language(chan))) {
-               ast_waitstream(chan, "");
+       if (!ast_test_flag(&opts, OPT_SILENT)) {
+               ast_stream_and_wait(chan, "agent-loginok", AST_DIGIT_NONE);
        }
 
        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_format_get_name(ast_channel_readformat(chan)),
+               ast_format_get_name(ast_channel_writeformat(chan)));
+       ast_channel_lock(chan);
        send_agent_login(chan, agent->username);
+       ast_channel_unlock(chan);
 
        agent_run(agent, chan);
        return -1;
@@ -2416,13 +2526,14 @@ static int action_agents(struct mansession *s, const struct message *m)
        struct ao2_iterator iter;
        struct agent_pvt *agent;
        struct ast_str *out = ast_str_alloca(4096);
+       int num_agents = 0;
 
        if (!ast_strlen_zero(id)) {
                snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
        } else {
                id_text[0] = '\0';
        }
-       astman_send_ack(s, m, "Agents will follow");
+       astman_send_listack(s, m, "Agents will follow", "start");
 
        iter = ao2_iterator_init(agents, 0);
        for (; (agent = ao2_iterator_next(&iter)); ao2_ref(agent, -1)) {
@@ -2477,12 +2588,12 @@ static int action_agents(struct mansession *s, const struct message *m)
                astman_append(s, "Event: Agents\r\n"
                        "%s%s\r\n",
                        ast_str_buffer(out), id_text);
+               ++num_agents;
        }
        ao2_iterator_destroy(&iter);
 
-       astman_append(s, "Event: AgentsComplete\r\n"
-               "%s"
-               "\r\n", id_text);
+       astman_send_list_complete_start(s, m, "AgentsComplete", num_agents);
+       astman_send_list_complete_end(s);
        return 0;
 }
 
@@ -2528,11 +2639,11 @@ 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();
-       ao2_ref(agents, -1);
+       ao2_cleanup(agents);
        agents = NULL;
        return 0;
 }
@@ -2546,15 +2657,9 @@ static int load_module(void)
        if (!agents) {
                return AST_MODULE_LOAD_FAILURE;
        }
-       if (load_config()) {
-               ast_log(LOG_ERROR, "Unable to load config. Not loading module.\n");
-               ao2_ref(agents, -1);
-               agents = NULL;
-               return AST_MODULE_LOAD_DECLINE;
-       }
 
        /* 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);
@@ -2577,6 +2682,13 @@ static int load_module(void)
                unload_module();
                return AST_MODULE_LOAD_FAILURE;
        }
+
+       if (load_config()) {
+               ast_log(LOG_ERROR, "Unable to load config. Not loading module.\n");
+               unload_module();
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
        return AST_MODULE_LOAD_SUCCESS;
 }
 
@@ -2590,6 +2702,7 @@ static int reload(void)
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Call center agent pool applications",
+       .support_level = AST_MODULE_SUPPORT_CORE,
        .load = load_module,
        .unload = unload_module,
        .reload = reload,