def holdtime_announce(agi, cursor, args):
    queue_id = agi.get_variable('XIVO_DSTID')
    try:
        queue = objects.Queue(agi, cursor, int(queue_id))
    except (ValueError, LookupError) as e:
        agi.dp_break(str(e))

    if queue.announce_holdtime != 1:
        return

    holdtime = agi.get_variable('QUEUEHOLDTIME')
    holdtime = max(1, (int(holdtime) + 59) / 60)

    gender = 'f' if holdtime == 1 else ''

    agi.answer()
    agi.stream_file('queue-holdtime')
    agi.stream_file('queue-less-than')
    agi.say_number(str(holdtime), gender=gender)
    agi.stream_file('queue-minutes')


def set_call_record_side(agi, queue):
    agi.set_variable('WAZO_CALL_RECORD_SIDE', 'caller')
    agi.set_variable('__WAZO_LOCAL_CHAN_MATCH_UUID', str(uuid4()))


agid.register(incoming_queue_set_features)
agid.register(holdtime_announce)
Esempio n. 2
0
# -*- coding: utf-8 -*-
# Copyright 2010-2019 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

import logging
from wazo_agid import agid
from wazo_agid import objects

logger = logging.getLogger(__name__)


def check_schedule(agi, cursor, args):
    path = agi.get_variable('XIVO_PATH')
    path_id = agi.get_variable('XIVO_PATH_ID')

    if not path:
        return

    schedule = objects.ScheduleDataMapper.get_from_path(cursor, path, path_id)
    schedule_state = schedule.compute_state_for_now()

    agi.set_variable('XIVO_SCHEDULE_STATUS', schedule_state.state)
    if schedule_state.state == 'closed':
        schedule_state.action.set_variables_in_agi(agi)

    # erase path for next schedule check
    agi.set_variable('XIVO_PATH', '')


agid.register(check_schedule)
# -*- coding: utf-8 -*-
# Copyright 2013-2019 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from wazo_agid import agid
from wazo_agid.handlers.agentfeatures import AgentFeatures


def incoming_agent_set_features(agi, cursor, args):
    agentfeatures_handler = AgentFeatures(agi, cursor, args)
    agentfeatures_handler.execute()


agid.register(incoming_agent_set_features)
Esempio n. 4
0
    if response['total'] != 1:
        raise Exception("Device with ip {} not found".format(ip))
    return response['items'][0]


def _get_line(client, provcode):
    response = client.lines.list(provisioning_code=provcode, recurse=True)
    if response['total'] != 1:
        raise Exception(
            "Line with provisioning code {} not found".format(provcode))
    return response['items'][0]


def provision(agi, cursor, args):
    try:
        client = agi.config['confd']['client']
        provcode = args[0]
        ip_port = args[1]
        if ':' in ip_port:
            ip, _ = ip_port.split(':', 1)
        else:
            ip = ip_port
        _do_provision(client, provcode, ip)
    except Exception as e:
        logger.error('Error during provisioning: %s', e)
    else:
        agi.set_variable('XIVO_PROV_OK', '1')


agid.register(provision)
Esempio n. 5
0
# -*- coding: utf-8 -*-
# Copyright 2012-2019 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

import logging
from wazo_agid import agid
from wazo_agid.handlers import agent

logger = logging.getLogger(__name__)


def agent_get_status(agi, cursor, args):
    try:
        tenant_uuid = args[0]
        agent_id = int(args[1])

        agent.get_agent_status(agi, agent_id, tenant_uuid=tenant_uuid)
    except Exception as e:
        logger.exception("Error while getting agent status")
        agi.dp_break(e)


agid.register(agent_get_status)
Esempio n. 6
0
            logger.debug("Creating backend, name %s, factory %s", section,
                         backend_factory)
            backends[section] = backend_factory(**backend_factory_args)
    logger.debug("Created %s backends", len(backends))

    # 4. creation destinations
    global DESTINATIONS
    DESTINATIONS = {}
    for section in filter(lambda s: s.startswith("dstnum_"), config.sections()):
        cur_destination = section[7:]  # 6 == len("dstnum_")
        cur_backend_ids = map(lambda s: s.strip(), config.get(section, "dest").split(","))
        cur_backends = _build_backends_list(backends, cur_backend_ids, cur_destination)
        logger.debug('Creating destination, dstnum %s, backends %s', cur_destination,
                     cur_backend_ids)
        DESTINATIONS[cur_destination] = cur_backends
    logger.debug("Created %s destinations", len(DESTINATIONS))


def _build_backends_list(available_backends, backend_ids, destination):
    backends = []
    for backend_id in backend_ids:
        if backend_id in available_backends:
            backends.append(available_backends[backend_id])
        else:
            logger.warning('Destination %s is referencing unknown backend "%s" in xivo_fax.conf',
                           destination, backend_id)
    return backends


agid.register(handle_fax, setup_handle_fax)
Esempio n. 7
0
# -*- coding: utf-8 -*-
# Copyright 2009-2019 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from wazo_agid import agid


def monitoring(agi, cursor, args):
    agi.send_command("Status: OK")


agid.register(monitoring)
Esempio n. 8
0
# -*- coding: utf-8 -*-
# Copyright 2019-2021 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from wazo_agid import agid


def wake_mobile(agi, cursor, args):
    user_uuid = args[0]
    should_wake_mobile = agi.get_variable('WAZO_WAIT_FOR_MOBILE') or False
    if not should_wake_mobile:
        return

    video_enabled = agi.get_variable('WAZO_VIDEO_ENABLED')
    agi.appexec(
        'UserEvent',
        'Pushmobile,WAZO_DST_UUID: {},WAZO_VIDEO_ENABLED: {}'.format(
            user_uuid, video_enabled))


agid.register(wake_mobile)
Esempio n. 9
0
        paging_opts = paging_opts + 'd'

    if paging.quiet:
        paging_opts = paging_opts + 'q'

    if paging.record:
        paging_opts = paging_opts + 'r'

    if paging.ignore:
        paging_opts = paging_opts + 'i'

    if paging.announcement_play and paging.announcement_file:
        sound_file_directory = '/var/lib/wazo/sounds/tenants'
        announcement_file_name = os.path.join(
            sound_file_directory,
            paging.tenant_uuid,
            'playback',
            paging.announcement_file,
        )

        paging_opts = paging_opts + u'A({file_name})'.format(
            file_name=announcement_file_name)

    if paging.announcement_caller:
        paging_opts = paging_opts + 'n'

    return paging_opts


agid.register(paging)
Esempio n. 10
0
        skill_rule_id = options[1]
        skill_rule_variables = options[2]

    if not skill_rule_id:
        _set_variables(agi, call, timeout)
        return

    with session_scope() as session:
        skill_rule = session.query(QueueSkillRule).get(int(skill_rule_id))
        if not skill_rule:
            _set_variables(agi, call, timeout)
            return

        skill_rule_function = 'skillrule-{}'.format(skill_rule.id)
        skill_rule_kwargs = []
        if skill_rule_variables:
            skill_rule_variables = skill_rule_variables.replace('|', ',')
            skill_rule_variables = json.loads(skill_rule_variables)
            skill_rule_kwargs = ['{}={}'.format(key, value) for key, value in skill_rule_variables.items()]
        call = '{function}({kwargs})'.format(function=skill_rule_function, kwargs=','.join(skill_rule_kwargs))

    _set_variables(agi, call, timeout)


def _set_variables(agi, call, timeout):
    agi.set_variable('XIVO_QUEUESKILLRULESET', call)
    agi.set_variable('ARG2_TIMEOUT', timeout)


agid.register(queue_skill_rule_set)
Esempio n. 11
0
            res = cursor.fetchall()
            call_rights.apply_rules(agi, res)

    if outcallid:
        cursor.query(
            "SELECT ${columns} FROM rightcall "
            "INNER JOIN rightcallmember "
            "ON rightcall.id = rightcallmember.rightcallid "
            "INNER JOIN outcall "
            "ON CAST(rightcallmember.typeval AS integer) = outcall.id "
            "WHERE rightcall.id IN " + rightcallids + " "
            "AND rightcallmember.type = 'outcall' "
            "AND outcall.id = %s "
            "AND rightcall.commented = 0",
            (call_rights.RIGHTCALL_AUTHORIZATION_COLNAME,
             call_rights.RIGHTCALL_PASSWD_COLNAME), (outcallid, ))
        res = cursor.fetchall()
        call_rights.apply_rules(agi, res)

    call_rights.allow(agi)


def user_set_call_rights(agi, cursor, args):
    try:
        _user_set_call_rights(agi, cursor, args)
    except call_rights.RuleAppliedException:
        return


agid.register(user_set_call_rights)
Esempio n. 12
0
        else:
            agi.set_variable('CALLERID(num)', callerid_num)

        return


def setup(cursor):
    global config

    re_objs.clear()
    config = ConfigParser.RawConfigParser()
    config.read([RULES_FILE])

    for section_name in config.sections():
        try:
            regexp = config.get(section_name, 'callerid')
        except ConfigParser.NoOptionError:
            log.error("option 'callerid' not found in section %r", section_name)
            sys.exit(1)

        try:
            re_obj = re.compile(regexp)
        except re.error:
            log.error("invalid regexp %r in section %r", regexp, section_name)
            sys.exit(1)

        re_objs[section_name] = re_obj


agid.register(in_callerid, setup)
Esempio n. 13
0
# -*- coding: utf-8 -*-
# Copyright 2012-2019 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

import logging
from wazo_agid import agid
from wazo_agid.handlers import agent

logger = logging.getLogger(__name__)


def agent_login(agi, cursor, args):
    try:
        tenant_uuid = args[0]
        agent_id = int(args[1])
        extension = args[2]
        context = args[3]

        agent.login_agent(agi, agent_id, extension, context, tenant_uuid)
    except Exception as e:
        logger.exception("Error while logging in agent")
        agi.dp_break(e)


agid.register(agent_login)
Esempio n. 14
0
def phone_progfunckey(agi, cursor, args):
    userid = agi.get_variable('XIVO_USERID')
    xlen = len(args)

    if xlen != 1:
        agi.dp_break("Invalid number of arguments (args: %r)" % args)

    try:
        fklist = split_extension(args[0])
    except ValueError as e:
        agi.dp_break(str(e))

    if userid != fklist[0]:
        agi.dp_break("Wrong userid. (userid: %r, excepted: %r)" %
                     (fklist[0], userid))

    feature = ""

    try:
        extenfeatures = objects.ExtenFeatures(agi, cursor)
        feature = extenfeatures.get_name_by_exten(fklist[1])
    except LookupError as e:
        feature = ""
        agi.verbose(str(e))

    agi.set_variable('XIVO_PHONE_PROGFUNCKEY', ''.join(fklist[1:]))
    agi.set_variable('XIVO_PHONE_PROGFUNCKEY_FEATURE', feature)


agid.register(phone_progfunckey)
Esempio n. 15
0
# -*- coding: utf-8 -*-
# Copyright 2012-2019 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

import logging
from wazo_agid import agid
from wazo_agid.handlers import agent

logger = logging.getLogger(__name__)


def agent_logoff(agi, cursor, args):
    try:
        tenant_uuid = args[0]
        agent_id = int(args[1])

        agent.logoff_agent(agi, agent_id, tenant_uuid=tenant_uuid)
    except Exception as e:
        logger.exception("Error while logging off agent")
        agi.dp_break(e)


agid.register(agent_logoff)
Esempio n. 16
0
                'Error while retrieving vmbox from number and context',
                exc_info=True)
            agi.dp_break(str(e))
    else:
        try:
            vmboxid = int(agi.get_variable('XIVO_VMBOXID'))
            vmbox = objects.VMBox(agi, cursor, vmboxid)
        except (ValueError, LookupError) as e:
            logger.error('Error while retrieving vmbox from id', exc_info=True)
            agi.dp_break(str(e))

    if vmbox.skipcheckpass:
        vmmain_options = "s"
    else:
        vmmain_options = ""

    if caller and caller.language:
        mbox_lang = caller.language
    elif vmbox.language:
        mbox_lang = vmbox.language
    else:
        mbox_lang = ''

    agi.set_variable('XIVO_VMMAIN_OPTIONS', vmmain_options)
    agi.set_variable('XIVO_MAILBOX', vmbox.mailbox)
    agi.set_variable('XIVO_MAILBOX_CONTEXT', vmbox.context)
    agi.set_variable('XIVO_MAILBOX_LANGUAGE', mbox_lang)


agid.register(vmbox_get_info)
Esempio n. 17
0
    else:
        _set_diversion(agi, '', '')


def _is_hold_time_overrun(agi, queue, waiting_calls):
    if queue.waittime is None or waiting_calls == 0:
        return False

    holdtime = int(agi.get_variable('QUEUEHOLDTIME'))
    return holdtime > queue.waittime


def _is_agent_ratio_overrun(agi, queue, waiting_calls):
    if queue.waitratio is None or waiting_calls == 0:
        return False

    agents = int(agi.get_variable('QUEUE_MEMBER({},logged)'.format(
        queue.name)))
    if agents == 0:
        return True

    return (waiting_calls + 1.0) / agents > queue.waitratio


def _set_diversion(agi, event, dialaction):
    agi.set_variable('XIVO_DIVERT_EVENT', event)
    agi.set_variable('XIVO_FWD_TYPE', 'QUEUE_' + dialaction)


agid.register(check_diversion)
Esempio n. 18
0
    tmpfile = filepath % "tmp"
    realfile = filepath % "outgoing"

    f = open(tmpfile, 'w')
    f.write("Channel: Local/%s@%s\n"
            "MaxRetries: 0\n"
            "RetryTime: 30\n"
            "WaitTime: 30\n"
            "CallerID: %s\n"
            "Set: XIVO_DISACONTEXT=%s\n"
            "Context: xivo-callbackdisa\n"
            "Extension: s" % (srcnum, context, srcnum, context))
    f.close()

    os.utime(tmpfile, (mtime, mtime))
    os.chown(tmpfile, ASTERISK_UID, ASTERISK_GID)
    os.rename(tmpfile, realfile)


def setup_callback(cursor):
    global ASTERISK_UID, ASTERISK_GID
    ASTERISK_UID, ASTERISK_GID = _get_uid_gid("asterisk")


def _get_uid_gid(name):
    pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell = pwd.getpwnam(name)
    return pw_uid, pw_gid


agid.register(callback, setup_callback)
Esempio n. 19
0
        logger.error('Error during enabling call recording: %s', e)


def _disable_call_recording(agi, calld, channel_id):
    try:
        calld.calls.stop_record(channel_id)
    except Exception as e:
        logger.error('Error during disabling call recording: %s', e)


def start_mix_monitor(agi, cursor, args):
    _start_mix_monitor(agi)


def _start_mix_monitor(agi):
    tenant_uuid = agi.get_variable(dialplan_variables.TENANT_UUID)
    recording_uuid = str(uuid.uuid4())
    filename = CALL_RECORDING_FILENAME_TEMPLATE.format(
        tenant_uuid=tenant_uuid,
        recording_uuid=recording_uuid,
    )
    mix_monitor_options = agi.get_variable('WAZO_MIXMONITOR_OPTIONS')

    agi.appexec('MixMonitor', '{},{}'.format(filename, mix_monitor_options))
    agi.set_variable('WAZO_CALL_RECORD_ACTIVE', '1')


agid.register(call_recording)
agid.register(record_caller)
agid.register(start_mix_monitor)
Esempio n. 20
0
# -*- coding: utf-8 -*-
# Copyright 2012-2019 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from wazo_agid import agid


def callerid_extend(agi, cursor, args):
    if 'agi_callington' in agi.env:
        agi.set_variable('XIVO_SRCTON', agi.env['agi_callington'])


agid.register(callerid_extend)
# -*- coding: utf-8 -*-
# Copyright 2021 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from wazo_agid import agid
from wazo_agid.handlers import queue


def queue_answered_call(agi, cursor, args):
    handler = queue.AnswerHandler(agi, cursor, args)
    handler.execute()


agid.register(queue_answered_call)
Esempio n. 22
0
MEETING_RE = re.compile(
    r'^wazo-meeting-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$'
)


def meeting_user(agi, cursor, args):
    tenant_uuid = agi.get_variable('WAZO_TENANT_UUID')
    try:
        meeting = _find_meeting(agi, cursor, tenant_uuid, args)
    except (AttributeError, LookupError, TypeError) as e:
        agi.verbose('Failed to find meeting {}'.format(e))
        agi.answer()
        agi.stream_file('invalid')
        return agi.dp_break('Could not find meeting matching {}'.format(args))

    agi.set_variable('WAZO_MEETING_UUID', meeting.uuid)
    agi.set_variable('WAZO_MEETING_NAME', meeting.name)


def _find_meeting(agi, cursor, tenant_uuid, args):
    identifier = args[0]

    if identifier.isdigit():
        return objects.Meeting(agi, cursor, tenant_uuid, number=identifier)
    else:
        matches = MEETING_RE.match(identifier)
        return objects.Meeting(agi, cursor, tenant_uuid, uuid=matches.group(1))


agid.register(meeting_user)
        self._user_uuid = user_uuid
        self.interfaces = []
        hint = agi.get_variable('HINT({}@usersharedlines)'.format(user_uuid))
        if not hint:
            raise UnknownUser()

        for endpoint in hint.split('&'):
            if '/' not in endpoint:
                continue

            for interface in self._find_matching_interfaces(endpoint):
                self.interfaces.append(interface)

    def _find_matching_interfaces(self, endpoint):
        protocol, name = endpoint.split('/', 1)
        if protocol == 'pjsip':
            contacts = build_sip_interface(self._agi, self._user_uuid, name)
            for contact in contacts.split('&'):
                yield contact
        else:
            yield endpoint


def get_user_interfaces(agi, cursor, args):
    user_uuid = args[0]
    user_line = _UserLine(agi, user_uuid)
    agi.set_variable('WAZO_USER_INTERFACES', '&'.join(user_line.interfaces))


agid.register(get_user_interfaces)
# -*- coding: utf-8 -*-
# Copyright 2021 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from wazo_agid import agid
from wazo_agid.handlers import group


def group_answered_call(agi, cursor, args):
    handler = group.AnswerHandler(agi, cursor, args)
    handler.execute()


agid.register(group_answered_call)
# -*- coding: utf-8 -*-
# Copyright 2006-2019 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

from wazo_agid import agid
from wazo_agid.handlers.userfeatures import UserFeatures


def incoming_user_set_features(agi, cursor, args):
    userfeatures_handler = UserFeatures(agi, cursor, args)
    userfeatures_handler.execute()


agid.register(incoming_user_set_features)
Esempio n. 26
0
def _phone_set_busy(agi, cursor, args):
    enabled = _phone_set_forward(agi, 'busy', args)
    if enabled is not None:
        agi.set_variable('XIVO_BUSYENABLED', int(enabled))


def _phone_set_forward(agi, forward_name, args):
    try:
        user_id = _get_id_of_calling_user(agi)
        result = _user_set_forward(agi, user_id, forward_name, args)
    except Exception as e:
        logger.error('Error during setting %s: %s', forward_name, e)
        return None
    else:
        agi.set_variable('XIVO_USERID_OWNER', user_id)
        return result['enabled']


def _user_set_forward(agi, user_id, forward_name, args):
    enabled = args[1] == '1'
    destination = args[2]
    confd_client = agi.config['confd']['client']
    body = {'enabled': enabled}
    if enabled:
        body['destination'] = destination
    confd_client.users(user_id).update_forward(forward_name, body)
    return body


agid.register(phone_set_feature)
Esempio n. 27
0
            _set_reverse_lookup_variable(agi, lookup_result['fields'])
    except Exception as e:
        msg = 'Reverse lookup failed: {}'.format(e)
        logger.info(msg)
        agi.verbose(msg)


def _should_reverse_lookup(cid_name, cid_number):
    return cid_name == cid_number or cid_name == 'unknown'


def _set_new_caller_id(agi, display_name, cid_number):
    new_caller_id = u'"{}" <{}>'.format(display_name, cid_number)
    agi.set_callerid(new_caller_id.encode('utf8'))


def _set_reverse_lookup_variable(agi, fields):
    agi.set_variable("XIVO_REVERSE_LOOKUP",
                     _create_reverse_lookup_variable(fields))


def _create_reverse_lookup_variable(fields):
    variable_content = []
    for key, value in fields.iteritems():
        variable_content.append(u'db-{}: {}'.format(key, value))

    return u','.join(variable_content).encode('utf8')


agid.register(callerid_forphones)
Esempio n. 28
0
def group_member_present(agi, cursors, args):
    tenant_uuid = args[0]
    user_uuid = args[1]
    group_id = int(args[2])

    confd_client = agi.config['confd']['client']

    try:
        group_name = confd_client.groups.get(group_id,
                                             tenant_uuid=tenant_uuid)['name']
    except RequestException as e:
        logger.error('Error while getting group %s in tenant %s: %s', group_id,
                     tenant_uuid, e)
        agi.set_variable('WAZO_GROUP_MEMBER_ERROR', e)
        return

    group_members = agi.get_variable(
        'QUEUE_MEMBER_LIST({group})'.format(group=group_name))
    group_members = group_members.split(',')

    interface = 'Local/{}@usersharedlines'.format(user_uuid)
    if interface in group_members:
        agi.set_variable('WAZO_GROUP_MEMBER_PRESENT', '1')
    else:
        agi.set_variable('WAZO_GROUP_MEMBER_PRESENT', '0')


agid.register(group_member_remove)
agid.register(group_member_add)
agid.register(group_member_present)
Esempio n. 29
0
            ringtype = CONFIG_PARSER.get(section, referer_origin_fwd)
        elif CONFIG_PARSER.has_option(section, referer_origin):
            ringtype = CONFIG_PARSER.get(section, referer_origin)
        elif forwarded == '1' and CONFIG_PARSER.has_option(
                section, origin_fwd):
            ringtype = CONFIG_PARSER.get(section, origin_fwd)
        elif forwarded == '1' and CONFIG_PARSER.has_option(section, 'forward'):
            ringtype = CONFIG_PARSER.get(section, 'forward')
        else:
            ringtype = CONFIG_PARSER.get(section, origin)

        phonetype = CONFIG_PARSER.get(section, 'phonetype')
    except (ConfigParser.NoOptionError, ValueError):
        logger.debug('Ring type exception', exc_info=True)
        agi.verbose("Using the native phone ring tone")
    else:
        agi.set_variable('XIVO_RINGTYPE', ringtype)
        agi.set_variable('XIVO_PHONETYPE', phonetype)
        agi.verbose("Using ring tone %s" % (ringtype, ))


def setup(cursor):
    global CONFIG_PARSER

    # This module is often called, keep this object alive.
    CONFIG_PARSER = ConfigParser.RawConfigParser()
    CONFIG_PARSER.readfp(open(CONFIG_FILE))


agid.register(getring, setup)
Esempio n. 30
0
from wazo_agid import agid

logger = logging.getLogger(__name__)


def fwdundoall(agi, cursor, args):
    user_id = _get_id_of_calling_user(agi)
    _user_disable_all_forwards(agi, user_id)


def _get_id_of_calling_user(agi):
    return int(agi.get_variable('XIVO_USERID'))


def _user_disable_all_forwards(agi, user_id):
    try:
        confd_client = agi.config['confd']['client']
        disabled = {'enabled': False}
        body = {
            'busy': disabled,
            'noanswer': disabled,
            'unconditional': disabled
        }
        confd_client.users(user_id).update_forwards(body)
    except Exception as e:
        logger.error('Error during disabling all forwards: %s', e)


agid.register(fwdundoall)