--- asterisk-1.4.15/res/res_agi.c	2007-10-01 14:56:28.000000000 -0500
+++ asterisk-1.4.15-patched/res/res_agi.c	2007-12-11 10:34:12.000000000 -0600
@@ -64,6 +64,7 @@
 #include "asterisk/lock.h"
 #include "asterisk/strings.h"
 #include "asterisk/agi.h"
+#include "asterisk/manager.h"
 
 #define MAX_ARGS 128
 #define MAX_COMMANDS 128
@@ -114,6 +115,7 @@
 enum agi_result {
 	AGI_RESULT_SUCCESS,
 	AGI_RESULT_SUCCESS_FAST,
+	AGI_RESULT_SUCCESS_ASYNC,
 	AGI_RESULT_FAILURE,
 	AGI_RESULT_HANGUP
 };
@@ -139,6 +141,352 @@
 	return res;
 }
 
+/* linked list of AGI commands ready to be executed by Async AGI */
+struct agi_cmd {
+	char *cmd_buffer;
+	char *cmd_id;
+	AST_LIST_ENTRY(agi_cmd) entry;
+};
+
+static void free_agi_cmd(struct agi_cmd *cmd)
+{
+	ast_free(cmd->cmd_buffer);
+	ast_free(cmd->cmd_id);
+	ast_free(cmd);
+}
+
+/* AGI datastore destructor */
+static void agi_destroy_commands_cb(void *data)
+{
+	struct agi_cmd *cmd;
+	AST_LIST_HEAD(, agi_cmd) *chan_cmds = data;
+	AST_LIST_LOCK(chan_cmds);
+	while ( (cmd = AST_LIST_REMOVE_HEAD(chan_cmds, entry)) ) { 
+		free_agi_cmd(cmd);
+	} 
+	AST_LIST_UNLOCK(chan_cmds);
+	AST_LIST_HEAD_DESTROY(chan_cmds);
+	ast_free(chan_cmds);
+}
+
+/* channel datastore to keep the queue of AGI commands in the channel */
+static const struct ast_datastore_info agi_commands_datastore_info = {
+	.type = "AsyncAGI",
+	.destroy = agi_destroy_commands_cb
+};
+
+static const char mandescr_asyncagi[] =
+"Description: Add an AGI command to the execute queue of the channel in Async AGI\n"
+"Variables:\n"
+"  *Channel: Channel that is currently in Async AGI\n"
+"  *Command: Application to execute\n"
+"   CommandID: comand id. This will be sent back in CommandID header of AsyncAGI exec event notification\n"
+"\n";
+
+static struct agi_cmd *get_agi_cmd(struct ast_channel *chan)
+{
+	struct ast_datastore *store;
+	struct agi_cmd *cmd;
+	ast_channel_lock(chan);
+	store = ast_channel_datastore_find(chan, &agi_commands_datastore_info, NULL);
+	ast_channel_unlock(chan);
+	AST_LIST_HEAD(, agi_cmd) *agi_commands;
+	if (!store) {
+		ast_log(LOG_ERROR, "Hu? datastore disappeared at Async AGI on Channel %s!\n", chan->name);
+		return NULL;
+	}
+	agi_commands = store->data;
+	AST_LIST_LOCK(agi_commands);
+	cmd = AST_LIST_REMOVE_HEAD(agi_commands, entry);
+	AST_LIST_UNLOCK(agi_commands);
+	return cmd;
+}
+
+/* channel is locked when calling this one either from the CLI or manager thread */
+static int add_agi_cmd(struct ast_channel *chan, const char *cmd_buff, const char *cmd_id)
+{
+	struct ast_datastore *store;
+	struct agi_cmd *cmd;
+	store = ast_channel_datastore_find(chan, &agi_commands_datastore_info, NULL);
+	AST_LIST_HEAD(, agi_cmd) *agi_commands;
+	if (!store) {
+		ast_log(LOG_WARNING, "Channel %s is not in Async AGI.\n", chan->name);
+		return -1;
+	}
+	agi_commands = store->data;
+	cmd = ast_calloc(1, sizeof(*cmd));
+	if (!cmd) {
+		return -1;
+	}
+	cmd->cmd_buffer = ast_strdup(cmd_buff);
+	if (!cmd->cmd_buffer) {
+		ast_free(cmd);
+		return -1;
+	}
+	cmd->cmd_id = ast_strdup(cmd_id);
+	if (!cmd->cmd_id) {
+		ast_free(cmd->cmd_buffer);
+		ast_free(cmd);
+		return -1;
+	}
+	AST_LIST_LOCK(agi_commands);
+	AST_LIST_INSERT_TAIL(agi_commands, cmd, entry);
+	AST_LIST_UNLOCK(agi_commands);
+	return 0;
+}
+
+static int add_to_agi(struct ast_channel *chan)
+{
+	struct ast_datastore *datastore;
+	AST_LIST_HEAD(, agi_cmd) *agi_cmds_list;
+
+	/* check if already on AGI */
+	ast_channel_lock(chan);
+	datastore = ast_channel_datastore_find(chan, &agi_commands_datastore_info, NULL);
+	ast_channel_unlock(chan);
+	if (datastore) {
+		return 0;
+	}
+
+	/* the channel has never been on Async AGI, lets allocate its datastore */
+	datastore = ast_channel_datastore_alloc(&agi_commands_datastore_info, "AGI");
+	if (!datastore) {
+		return -1;
+	}
+	agi_cmds_list = ast_calloc(1, sizeof(*agi_cmds_list));
+	if (!agi_cmds_list) {
+		ast_log(LOG_ERROR, "Unable to allocate Async AGI commands list.\n");
+		ast_channel_datastore_free(datastore);
+		return -1;
+	}
+	datastore->data = agi_cmds_list;
+	AST_LIST_HEAD_INIT(agi_cmds_list);
+	ast_channel_lock(chan);
+	ast_channel_datastore_add(chan, datastore);
+	ast_channel_unlock(chan);
+	return 0;
+}
+
+/*!
+ * \brief CLI command to add applications to execute in Async AGI
+ * \param e
+ * \param cmd 
+ * \param a
+ *
+ * \retval RESULT_SUCCESS on success
+*/
+static int handle_agi_exec(int fd, int argc, char *argv[])
+{
+	struct ast_channel *chan;
+	if (argc < 4)
+		return RESULT_SHOWUSAGE;
+	chan = ast_get_channel_by_name_locked(argv[2]);
+	if (!chan) {
+		ast_log(LOG_WARNING, "Channel %s does not exists or cannot lock it\n", argv[2]);
+		return RESULT_FAILURE;
+	}
+	if (add_agi_cmd(chan, argv[3], (argc > 4 ? argv[4] : ""))) {
+		ast_log(LOG_WARNING, "failed to add AGI command to the queue of channel %s\n", chan->name);
+		ast_channel_unlock(chan);
+		return RESULT_FAILURE;
+	}
+	ast_log(LOG_DEBUG, "Added AGI command to channel %s queue\n", chan->name);
+	ast_channel_unlock(chan);
+	return RESULT_SUCCESS;
+}
+
+/*!
+ * \brief Add a new command to execute by the Async AGI application
+ * \param s
+ * \param m
+ *
+ * It will append the application to the specified channel's queue
+ * if the channel is not inside Async AGI application it will return an error
+ * \retval 0 on success or incorrect use
+ * \retval 1 on failure to add the command ( most likely because the channel is not on Async AGI loop )
+*/
+static int action_add_agi_cmd(struct mansession *s, const struct message *m)
+{
+	const char *channel = astman_get_header(m, "Channel");
+	const char *cmdbuff = astman_get_header(m, "Command");
+	const char *cmdid   = astman_get_header(m, "CommandID");
+	struct ast_channel *chan;
+	char buf[256];
+	if (ast_strlen_zero(channel) || ast_strlen_zero(cmdbuff)) {
+		astman_send_error(s, m, "Both, Channel and Command are *required*");
+		return 0;
+	}
+	chan = ast_get_channel_by_name_locked(channel);
+	if (!chan) {
+		snprintf(buf, sizeof(buf), "Channel %s does not exists or cannot get its lock", channel);
+		astman_send_error(s, m, buf);
+		return 1;
+	}
+	if (add_agi_cmd(chan, cmdbuff, cmdid)) {
+		snprintf(buf, sizeof(buf), "Failed to add AGI command to channel %s queue", chan->name);
+		astman_send_error(s, m, buf);
+		ast_channel_unlock(chan);
+		return 1;
+	}
+	astman_send_ack(s, m, "Added AGI command to queue");
+	ast_channel_unlock(chan);
+	return 0;
+}
+
+static char hex_encode(int i)
+{
+	i &= 0xf;
+	char c = (char)i;
+	if (i < 10) 
+		return '0' + c;
+	i -= 10;
+	c = (char)i;
+	return 'a' + c;
+}
+
+static void url_encode(char *in, char *out, int maxlen)
+{
+	char *orig_out = out;
+	int i;
+	maxlen -= 6;
+	while (*in && (out - orig_out) < maxlen) {
+		i = *in;
+		i &= 255;
+		in++;
+		if (i <= ' ' || i >= 127 || i == '&' || i == '?') {
+			*(out++) = '%';
+			*(out++) = hex_encode(i >> 4);
+			*(out++) = hex_encode(i);
+		} else {
+			*out = (char) i;
+			out++;
+		}
+	}
+	*out = 0;
+}
+
+static int agi_handle_command(struct ast_channel *chan, AGI *agi, char *buf);
+static void setup_env(struct ast_channel *chan, char *request, int fd, int enhanced);
+static enum agi_result launch_asyncagi(struct ast_channel *chan, char *argv[], int *efd, int *opid)
+{
+/* This buffer sizes might cause truncation if the AGI command writes more data than AGI_BUF_SIZE as result. But let's be serious, is there an AGI
+   command that write a response larger than 1024 bytes?, I don't think so, most of them are just result=blah stuff. However probably if GET VARIABLE
+   is called and the variable has large amount of data, that could be a problem. We could make this buffers dynamic, but let's leave that as a second step.
+   
+   AMI_BUF_SIZE is twice AGI_BUF_SIZE just for the sake of choosing a safe number. Some characters of AGI buf will be url encoded to be sent to manager clients. 
+   An URL encoded character will take 3 bytes, but again, to cause truncation more than about 70% of the AGI buffer should be URL encoded for that to happen. 
+   Not likely at all. 
+   
+   On the other hand. I wonder if read() could eventually return less data than the amount already available in the pipe? If so, how to detect that? 
+   So far, my tests on Linux have not had any problems.
+*/
+#define AGI_BUF_SIZE 1024
+#define AMI_BUF_SIZE 2048
+	struct ast_frame *f;
+	struct agi_cmd *cmd;
+	int res, fds[2];
+	int timeout = 100; 
+	char agi_buffer[AGI_BUF_SIZE + 1];
+	char ami_buffer[AMI_BUF_SIZE];
+	enum agi_result returnstatus = AGI_RESULT_SUCCESS_ASYNC;
+	AGI async_agi;
+
+	if (efd) {
+		ast_log(LOG_WARNING, "Async AGI does not support Enhanced AGI yet\n");
+		return AGI_RESULT_FAILURE;
+	}
+
+	/* add AsyncAGI datastore to the channel */
+	if (add_to_agi(chan)) {
+		ast_log(LOG_ERROR, "failed to start Async AGI on channel %s\n", chan->name);
+		return AGI_RESULT_FAILURE;
+	}	
+
+	/* this pipe allows us to create a "fake" AGI struct to use the AGI commands */
+	res = pipe(fds);
+	if (res) {
+		ast_log(LOG_ERROR, "failed to create Async AGI pipe\n");
+		/* intentionally do not remove datastore, added with add_to_agi(), from channel */
+		return AGI_RESULT_FAILURE;
+	}
+	async_agi.fd = fds[1]; /* handlers will get the pipe write file descriptor */
+	async_agi.ctrl = fds[1];
+	async_agi.audio = -1; /* no audio support */
+	async_agi.fast = 0;
+
+	/* notify possible manager users of a new channel ready to receive commands */	
+	setup_env(chan, "async", fds[1], 0);
+	res = read(fds[0], agi_buffer, AGI_BUF_SIZE);
+	if (!res) {
+		ast_log(LOG_ERROR, "failed to read from Async AGI pipe on channel %s\n", chan->name);
+		returnstatus = AGI_RESULT_FAILURE;
+		goto quit;
+	}
+	agi_buffer[res] = '\0';
+	url_encode(agi_buffer, ami_buffer, AMI_BUF_SIZE);
+	manager_event(EVENT_FLAG_AGI, "AsyncAGI", "SubEvent: Start\r\nChannel: %s\r\nEnv: %s\r\n", chan->name, ami_buffer); 
+	while (1) {
+		if (ast_check_hangup(chan)) {
+			ast_log(LOG_DEBUG, "ast_check_hangup returned true on chan %s\n", chan->name);
+			break;
+		}
+		cmd = get_agi_cmd(chan);
+		if (cmd) {
+			res = agi_handle_command(chan, &async_agi, cmd->cmd_buffer);
+			if ((res < 0) || (res == AST_PBX_KEEPALIVE)) {
+				free_agi_cmd(cmd);
+				break;
+			}
+			res = read(fds[0], agi_buffer, AGI_BUF_SIZE);
+			if (!res) {
+				returnstatus = AGI_RESULT_FAILURE;
+				ast_log(LOG_ERROR, "failed to read from AsyncAGI pipe on channel %s\n", chan->name);
+				free_agi_cmd(cmd);
+				break;
+			}
+			agi_buffer[res] = '\0';
+			url_encode(agi_buffer, ami_buffer, AMI_BUF_SIZE);
+			if (ast_strlen_zero(cmd->cmd_id))
+				manager_event(EVENT_FLAG_AGI, "AsyncAGI", "SubEvent: Exec\r\nChannel: %s\r\nResult: %s\r\n", chan->name, ami_buffer);
+			else
+				manager_event(EVENT_FLAG_AGI, "AsyncAGI", "SubEvent: Exec\r\nChannel: %s\r\nCommandID: %s\r\nResult: %s\r\n", chan->name, cmd->cmd_id, ami_buffer);
+			free_agi_cmd(cmd);
+		} else {
+			res = ast_waitfor(chan, timeout);
+			if (res < 0) {
+				ast_log(LOG_DEBUG, "ast_waitfor returned <= 0 on chan %s\n", chan->name);
+				break;
+			}
+			if (res == 0)
+				continue;
+			f = ast_read(chan);
+			if (!f) {
+				ast_log(LOG_DEBUG, "No frame read on channel %s, going out ...\n", chan->name);
+				returnstatus = AGI_RESULT_HANGUP;
+				break;
+			}	
+			if (f->frametype == AST_FRAME_CONTROL && f->subclass == AST_CONTROL_HANGUP) {
+				ast_log(LOG_DEBUG, "Got HANGUP frame on channel %s, going out ...\n", chan->name);
+				ast_frfree(f);
+				break;
+			}
+			ast_frfree(f);
+		}
+	}
+quit:
+	/* notify manager users this channel cannot be controlled anymore by Async AGI */
+	manager_event(EVENT_FLAG_AGI, "AsyncAGI", "SubEvent: End\r\nChannel: %s\r\n", chan->name);
+
+	close(fds[0]);
+	close(fds[1]);
+
+	/* intentionally don't get rid of the datastore. So commands can be still in the queue in case AsyncAGI gets called again.
+	   Datastore destructor will be called on channel destroy anyway  */
+
+	return returnstatus;
+
+}
+
 /* launch_netscript: The fastagi handler.
 	FastAGI defaults to port 4573 */
 static enum agi_result launch_netscript(char *agiurl, char *argv[], int *fds, int *efd, int *opid)
@@ -236,7 +584,7 @@
 	return AGI_RESULT_SUCCESS_FAST;
 }
 
-static enum agi_result launch_script(char *script, char *argv[], int *fds, int *efd, int *opid)
+static enum agi_result launch_script(struct ast_channel *chan, char *script, char *argv[], int *fds, int *efd, int *opid)
 {
 	char tmp[256];
 	int pid;
@@ -249,6 +597,8 @@
 	
 	if (!strncasecmp(script, "agi://", 6))
 		return launch_netscript(script, argv, fds, efd, opid);
+	if (!strncasecmp(script, "agi:async", sizeof("agi:async")-1))
+		return launch_asyncagi(chan, argv, efd, opid);
 	
 	if (script[0] != '/') {
 		snprintf(tmp, sizeof(tmp), "%s/%s", (char *)ast_config_AST_AGI_DIR, script);
@@ -1309,6 +1659,10 @@
 "Usage: agi debug\n"
 "       Enables dumping of AGI transactions for debugging purposes\n";
 
+static char agi_exec_help[] =
+"Usage: agi exec <channel name> <app and arguments> [id]\n"
+"       Add AGI command to the execute queue of the specified channel in Async AGI\n";
+
 static char no_debug_usage[] = 
 "Usage: agi debug off\n"
 "       Disables dumping of AGI transactions for debugging purposes\n";
@@ -1356,6 +1710,12 @@
 	return RESULT_SUCCESS;
 }
 
+static int handle_break_asyncagi(struct ast_channel *chan, AGI *agi, int argc, char *argv[])
+{
+	fdprintf(agi->fd, "200 result=0\n");
+	return AST_PBX_KEEPALIVE;
+}
+
 static char usage_setmusic[] =
 " Usage: SET MUSIC ON <on|off> <class>\n"
 "	Enables/Disables the music on hold generator.  If <class> is\n"
@@ -1603,6 +1963,10 @@
 " Usage: NoOp\n"
 "	Does nothing.\n";
 
+static char usage_break_aagi[] = 
+" Usage: BreakAsyncAGI\n"
+" Break the Async AGI loop.\n";
+
 static agi_command commands[MAX_COMMANDS] = {
 	{ { "answer", NULL }, handle_answer, "Answer channel", usage_answer },
 	{ { "channel", "status", NULL }, handle_channelstatus, "Returns status of the connected channel", usage_channelstatus },
@@ -1641,6 +2005,7 @@
 	{ { "tdd", "mode", NULL }, handle_tddmode, "Toggles TDD mode (for the deaf)", usage_tddmode },
 	{ { "verbose", NULL }, handle_verbose, "Logs a message to the asterisk verbose log", usage_verbose },
 	{ { "wait", "for", "digit", NULL }, handle_waitfordigit, "Waits for a digit to be pressed", usage_waitfordigit },
+	{ { "break", "asyncagi", NULL }, handle_break_asyncagi, "Break AsyncAGI loop", usage_break_aagi },
 };
 
 static int help_workhorse(int fd, char *match[])
@@ -2047,7 +2412,7 @@
 		}
 	}
 #endif
-	res = launch_script(argv[0], argv, fds, enhanced ? &efd : NULL, &pid);
+	res = launch_script(chan, argv[0], argv, fds, enhanced ? &efd : NULL, &pid);
 	if (res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) {
 		int status = 0;
 		agi.fd = fds[1];
@@ -2069,6 +2434,7 @@
 	switch (res) {
 	case AGI_RESULT_SUCCESS:
 	case AGI_RESULT_SUCCESS_FAST:
+	case AGI_RESULT_SUCCESS_ASYNC:
 		pbx_builtin_setvar_helper(chan, "AGISTATUS", "SUCCESS");
 		break;
 	case AGI_RESULT_FAILURE:
@@ -2117,6 +2483,11 @@
 	return agi_exec_full(chan, data, 0, 1);
 }
 
+static char *complete_ch_3(const char *line, const char *word, int pos, int state)
+{
+	return ast_complete_channels(line, word, pos, state, 2);
+}
+
 static char showagi_help[] =
 "Usage: agi show [topic]\n"
 "       When called with a topic as an argument, displays usage\n"
@@ -2152,6 +2523,10 @@
 	agi_no_debug, "Disable AGI debugging",
 	no_debug_usage, NULL, &cli_agi_no_debug_deprecated },
 
+	{ { "agi", "exec", NULL },
+	handle_agi_exec, "Add AGI command to a channel in Async AGI",
+	agi_exec_help, complete_ch_3},
+
 	{ { "agi", "show", NULL },
 	handle_showagi, "List AGI commands or specific help",
 	showagi_help, NULL, &cli_show_agi_deprecated },
@@ -2167,6 +2542,7 @@
 	ast_cli_unregister_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry));
 	ast_unregister_application(eapp);
 	ast_unregister_application(deadapp);
+	ast_manager_unregister("AGI");
 	return ast_unregister_application(app);
 }
 
@@ -2175,6 +2551,7 @@
 	ast_cli_register_multiple(cli_agi, sizeof(cli_agi) / sizeof(struct ast_cli_entry));
 	ast_register_application(deadapp, deadagi_exec, deadsynopsis, descrip);
 	ast_register_application(eapp, eagi_exec, esynopsis, descrip);
+	ast_manager_register2("AGI", EVENT_FLAG_AGI, action_add_agi_cmd, "Add an AGI command to execute by Async AGI", mandescr_asyncagi);
 	return ast_register_application(app, agi_exec, synopsis, descrip);
 }
 
--- asterisk-1.4.15/main/manager.c	2007-11-28 16:30:46.000000000 -0600
+++ asterisk-1.4.15-patched/main/manager.c	2007-12-11 10:30:08.000000000 -0600
@@ -126,6 +126,7 @@
 	{ EVENT_FLAG_AGENT, "agent" },
 	{ EVENT_FLAG_USER, "user" },
 	{ EVENT_FLAG_CONFIG, "config" },
+	{ EVENT_FLAG_AGI, "agi" },
 	{ -1, "all" },
 	{ 0, "none" },
 };
--- asterisk-1.4.15/include/asterisk/manager.h	2007-04-06 15:58:43.000000000 -0500
+++ asterisk-1.4.15-patched/include/asterisk/manager.h	2007-12-11 10:30:08.000000000 -0600
@@ -55,6 +55,7 @@
 #define EVENT_FLAG_AGENT		(1 << 5) /* Ability to read/set agent info */
 #define EVENT_FLAG_USER                 (1 << 6) /* Ability to read/set user info */
 #define EVENT_FLAG_CONFIG		(1 << 7) /* Ability to modify configurations */
+#define EVENT_FLAG_AGI                  (1 << 8) /* Ability to execute AGI and receive Async AGI events */
 
 /* Export manager structures */
 #define AST_MAX_MANHEADERS 128
