<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>
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) {
ast_party_connected_line_free(&doomed->waiting_colp);
if (doomed->caller_bridge) {
- ast_bridge_destroy(doomed->caller_bridge, AST_CAUSE_USER_BUSY);
+ ast_bridge_destroy(doomed->caller_bridge, 0);
doomed->caller_bridge = NULL;
}
if (doomed->logged) {
/*!
* \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
*
NULL, 0);
if (res) {
/* Reset agent. */
- ast_bridge_destroy(caller_bridge, AST_CAUSE_USER_BUSY);
+ ast_bridge_destroy(caller_bridge, 0);
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);
+ 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;
+ }
if (record_agent_calls) {
struct ast_bridge_features_automixmonitor 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_channel *bridge_channel, void *hook_pvt)
agent_devstate_changed(agent->username);
if (caller_bridge) {
- ast_bridge_destroy(caller_bridge, AST_CAUSE_USER_BUSY);
+ ast_bridge_destroy(caller_bridge, 0);
}
ast_channel_lock(logged);
agent_unlock(agent);
ao2_ref(cfg_old, -1);
if (caller_bridge) {
- ast_bridge_destroy(caller_bridge, AST_CAUSE_USER_BUSY);
+ ast_bridge_destroy(caller_bridge, 0);
}
if (agent->state == AGENT_STATE_LOGGING_OUT
agent->caller_bridge = NULL;
agent_unlock(agent);
if (caller_bridge) {
- ast_bridge_destroy(caller_bridge, AST_CAUSE_USER_BUSY);
+ ast_bridge_destroy(caller_bridge, 0);
}
return;
}
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_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END,
- AST_CAUSE_USER_BUSY);
+ 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);
}
}
/*!
+ * \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.
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, 0);
ast_bridge_features_cleanup(&caller_features);
ast_verb(3, "Agent '%s' not logged in.\n", agent->username);
break;
default:
agent_unlock(agent);
- ast_party_connected_line_free(&connected);
ast_bridge_destroy(caller_bridge, 0);
ast_bridge_features_cleanup(&caller_features);
ast_verb(3, "Agent '%s' is busy.\n", agent->username);
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);
+ 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, 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");
+ 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,
- AST_BRIDGE_JOIN_PASS_REFERENCE);
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;
}
/*!
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,