ServiceNow-AI/servicenow-tasks huggingface dataset:
Task: workarena.servicenow.all-menu
Goal: Navigate to the "Indicators > Indicator Groups" module of the "Performance Analytics" application.
Task configuration: final_url: /now/nav/ui/classic/params/target/pa_tags_list.do

Validation code:
import re
import urllib.parse as parse
from typing import Any, Dict, List


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Extract URLs from the last message and compare them to config["final_url"].
    Returns 1 if any URL matches (after unquoting and canonical parse/unparse), else 0.
    """
    if "final_url" not in config:
        raise ValueError("Target must contain 'final_url'")
    if not messages:
        raise ValueError("Messages must not be empty")

    content = messages[-1].get("content", "")

    # --- Step 1: Extract all URLs — markdown links first, then bare URLs ---
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)

    if not urls:
        return 0

    final_url = parse.unquote(config["final_url"])

    # --- Step 2: Check each URL for a match ---
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        parsed = parse.urlparse(current_url)
        current_path = parsed.path

        # --- Step 3: Compare path/URL ---
        if final_url.startswith("http"):
            canonical_final = parse.urlunparse(parse.urlparse(final_url))
            if current_url != canonical_final:
                continue
        elif final_url.startswith("/"):
            final_parsed = parse.urlparse(final_url)
            if current_path != final_parsed.path:
                continue
        else:
            if final_url not in current_path:
                continue

        # --- Step 4: If config has a "query" field, check each key=value is in the URL ---
        if "query" in config and config["query"]:
            url_query = parse.unquote(parsed.query)
            query_params = parse.parse_qs(parsed.query)
            sysparm_query = parse.unquote(query_params.get("sysparm_query", [""])[0])
            sysparm_query = sysparm_query.removesuffix("^EQ")
            sysparm_query = sysparm_query.replace("^EQ", "^")

            query_matched = True
            for part in config["query"].split("^"):
                part = part.strip()
                if not part:
                    continue
                if part not in url_query and part not in sysparm_query:
                    query_matched = False
                    break

            if not query_matched:
                continue

        return 1

    return 0
Task: workarena.servicenow.all-menu
Goal: Navigate to the "CMDB Properties > Duplicate CI Remediator Properties" module of the "Configuration" application.
Task configuration: final_url: /now/nav/ui/classic/params/target/system_properties_ui.do%3Fsysparm_title%3DCMDB%2520Properties%26sysparm_category%3DDedup

Validation code:
import re
import urllib.parse as parse
from typing import Any, Dict, List


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Extract URLs from the last message and compare them to config["final_url"].
    Returns 1 if any URL matches (after unquoting and canonical parse/unparse), else 0.
    """
    if "final_url" not in config:
        raise ValueError("Target must contain 'final_url'")
    if not messages:
        raise ValueError("Messages must not be empty")

    content = messages[-1].get("content", "")

    # --- Step 1: Extract all URLs — markdown links first, then bare URLs ---
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)

    if not urls:
        return 0

    final_url = parse.unquote(config["final_url"])

    # --- Step 2: Check each URL for a match ---
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        parsed = parse.urlparse(current_url)
        current_path = parsed.path

        # --- Step 3: Compare path/URL ---
        if final_url.startswith("http"):
            canonical_final = parse.urlunparse(parse.urlparse(final_url))
            if current_url != canonical_final:
                continue
        elif final_url.startswith("/"):
            final_parsed = parse.urlparse(final_url)
            if current_path != final_parsed.path:
                continue
        else:
            if final_url not in current_path:
                continue

        # --- Step 4: If config has a "query" field, check each key=value is in the URL ---
        if "query" in config and config["query"]:
            url_query = parse.unquote(parsed.query)
            query_params = parse.parse_qs(parsed.query)
            sysparm_query = parse.unquote(query_params.get("sysparm_query", [""])[0])
            sysparm_query = sysparm_query.removesuffix("^EQ")
            sysparm_query = sysparm_query.replace("^EQ", "^")

            query_matched = True
            for part in config["query"].split("^"):
                part = part.strip()
                if not part:
                    continue
                if part not in url_query and part not in sysparm_query:
                    query_matched = False
                    break

            if not query_matched:
                continue

        return 1

    return 0
Task: workarena.servicenow.all-menu
Goal: Navigate to the "Portfolios > License Assets" module of the "Asset" application.
Task configuration: final_url: /now/nav/ui/classic/params/target/alm_license_list.do

Validation code:
import re
import urllib.parse as parse
from typing import Any, Dict, List


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Extract URLs from the last message and compare them to config["final_url"].
    Returns 1 if any URL matches (after unquoting and canonical parse/unparse), else 0.
    """
    if "final_url" not in config:
        raise ValueError("Target must contain 'final_url'")
    if not messages:
        raise ValueError("Messages must not be empty")

    content = messages[-1].get("content", "")

    # --- Step 1: Extract all URLs — markdown links first, then bare URLs ---
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)

    if not urls:
        return 0

    final_url = parse.unquote(config["final_url"])

    # --- Step 2: Check each URL for a match ---
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        parsed = parse.urlparse(current_url)
        current_path = parsed.path

        # --- Step 3: Compare path/URL ---
        if final_url.startswith("http"):
            canonical_final = parse.urlunparse(parse.urlparse(final_url))
            if current_url != canonical_final:
                continue
        elif final_url.startswith("/"):
            final_parsed = parse.urlparse(final_url)
            if current_path != final_parsed.path:
                continue
        else:
            if final_url not in current_path:
                continue

        # --- Step 4: If config has a "query" field, check each key=value is in the URL ---
        if "query" in config and config["query"]:
            url_query = parse.unquote(parsed.query)
            query_params = parse.parse_qs(parsed.query)
            sysparm_query = parse.unquote(query_params.get("sysparm_query", [""])[0])
            sysparm_query = sysparm_query.removesuffix("^EQ")
            sysparm_query = sysparm_query.replace("^EQ", "^")

            query_matched = True
            for part in config["query"].split("^"):
                part = part.strip()
                if not part:
                    continue
                if part not in url_query and part not in sysparm_query:
                    query_matched = False
                    break

            if not query_matched:
                continue

        return 1

    return 0
Task: workarena.servicenow.all-menu
Goal: Navigate to the "Hub" module of the "Configuration by Category" application.
Task configuration: final_url: /now/nav/ui/classic/params/target/cmdb_ci_hub_network_list.do

Validation code:
import re
import urllib.parse as parse
from typing import Any, Dict, List


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Extract URLs from the last message and compare them to config["final_url"].
    Returns 1 if any URL matches (after unquoting and canonical parse/unparse), else 0.
    """
    if "final_url" not in config:
        raise ValueError("Target must contain 'final_url'")
    if not messages:
        raise ValueError("Messages must not be empty")

    content = messages[-1].get("content", "")

    # --- Step 1: Extract all URLs — markdown links first, then bare URLs ---
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)

    if not urls:
        return 0

    final_url = parse.unquote(config["final_url"])

    # --- Step 2: Check each URL for a match ---
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        parsed = parse.urlparse(current_url)
        current_path = parsed.path

        # --- Step 3: Compare path/URL ---
        if final_url.startswith("http"):
            canonical_final = parse.urlunparse(parse.urlparse(final_url))
            if current_url != canonical_final:
                continue
        elif final_url.startswith("/"):
            final_parsed = parse.urlparse(final_url)
            if current_path != final_parsed.path:
                continue
        else:
            if final_url not in current_path:
                continue

        # --- Step 4: If config has a "query" field, check each key=value is in the URL ---
        if "query" in config and config["query"]:
            url_query = parse.unquote(parsed.query)
            query_params = parse.parse_qs(parsed.query)
            sysparm_query = parse.unquote(query_params.get("sysparm_query", [""])[0])
            sysparm_query = sysparm_query.removesuffix("^EQ")
            sysparm_query = sysparm_query.replace("^EQ", "^")

            query_matched = True
            for part in config["query"].split("^"):
                part = part.strip()
                if not part:
                    continue
                if part not in url_query and part not in sysparm_query:
                    query_matched = False
                    break

            if not query_matched:
                continue

        return 1

    return 0
Task: workarena.servicenow.all-menu
Goal: Navigate to the "Administration > Guided Setup" module of the "Knowledge" application.
Task configuration: final_url: /now/nav/ui/classic/params/target/%24guided_setup.do%23%2Fcontent%2F2a9599a67310230021741c86fbf6a741

Validation code:
import re
import urllib.parse as parse
from typing import Any, Dict, List


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Extract URLs from the last message and compare them to config["final_url"].
    Returns 1 if any URL matches (after unquoting and canonical parse/unparse), else 0.
    """
    if "final_url" not in config:
        raise ValueError("Target must contain 'final_url'")
    if not messages:
        raise ValueError("Messages must not be empty")

    content = messages[-1].get("content", "")

    # --- Step 1: Extract all URLs — markdown links first, then bare URLs ---
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)

    if not urls:
        return 0

    final_url = parse.unquote(config["final_url"])

    # --- Step 2: Check each URL for a match ---
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        parsed = parse.urlparse(current_url)
        current_path = parsed.path

        # --- Step 3: Compare path/URL ---
        if final_url.startswith("http"):
            canonical_final = parse.urlunparse(parse.urlparse(final_url))
            if current_url != canonical_final:
                continue
        elif final_url.startswith("/"):
            final_parsed = parse.urlparse(final_url)
            if current_path != final_parsed.path:
                continue
        else:
            if final_url not in current_path:
                continue

        # --- Step 4: If config has a "query" field, check each key=value is in the URL ---
        if "query" in config and config["query"]:
            url_query = parse.unquote(parsed.query)
            query_params = parse.parse_qs(parsed.query)
            sysparm_query = parse.unquote(query_params.get("sysparm_query", [""])[0])
            sysparm_query = sysparm_query.removesuffix("^EQ")
            sysparm_query = sysparm_query.replace("^EQ", "^")

            query_matched = True
            for part in config["query"].split("^"):
                part = part.strip()
                if not part:
                    continue
                if part not in url_query and part not in sysparm_query:
                    query_matched = False
                    break

            if not query_matched:
                continue

        return 1

    return 0
Task: workarena.servicenow.all-menu
Goal: Navigate to the "Live Clones > Clone History" module of the "System Clone" application.
Task configuration: final_url: /now/nav/ui/classic/params/target/clone_instance_list.do

Validation code:
import re
import urllib.parse as parse
from typing import Any, Dict, List


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Extract URLs from the last message and compare them to config["final_url"].
    Returns 1 if any URL matches (after unquoting and canonical parse/unparse), else 0.
    """
    if "final_url" not in config:
        raise ValueError("Target must contain 'final_url'")
    if not messages:
        raise ValueError("Messages must not be empty")

    content = messages[-1].get("content", "")

    # --- Step 1: Extract all URLs — markdown links first, then bare URLs ---
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)

    if not urls:
        return 0

    final_url = parse.unquote(config["final_url"])

    # --- Step 2: Check each URL for a match ---
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        parsed = parse.urlparse(current_url)
        current_path = parsed.path

        # --- Step 3: Compare path/URL ---
        if final_url.startswith("http"):
            canonical_final = parse.urlunparse(parse.urlparse(final_url))
            if current_url != canonical_final:
                continue
        elif final_url.startswith("/"):
            final_parsed = parse.urlparse(final_url)
            if current_path != final_parsed.path:
                continue
        else:
            if final_url not in current_path:
                continue

        # --- Step 4: If config has a "query" field, check each key=value is in the URL ---
        if "query" in config and config["query"]:
            url_query = parse.unquote(parsed.query)
            query_params = parse.parse_qs(parsed.query)
            sysparm_query = parse.unquote(query_params.get("sysparm_query", [""])[0])
            sysparm_query = sysparm_query.removesuffix("^EQ")
            sysparm_query = sysparm_query.replace("^EQ", "^")

            query_matched = True
            for part in config["query"].split("^"):
                part = part.strip()
                if not part:
                    continue
                if part not in url_query and part not in sysparm_query:
                    query_matched = False
                    break

            if not query_matched:
                continue

        return 1

    return 0
Task: workarena.servicenow.all-menu
Goal: Navigate to the "Stock > In Stock" module of the "Inventory" application.
Task configuration: final_url: /now/nav/ui/classic/params/target/alm_asset_list.do
query: install_status=6

Validation code:
import re
import urllib.parse as parse
from typing import Any, Dict, List


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Extract URLs from the last message and compare them to config["final_url"].
    Returns 1 if any URL matches (after unquoting and canonical parse/unparse), else 0.
    """
    if "final_url" not in config:
        raise ValueError("Target must contain 'final_url'")
    if not messages:
        raise ValueError("Messages must not be empty")

    content = messages[-1].get("content", "")

    # --- Step 1: Extract all URLs — markdown links first, then bare URLs ---
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)

    if not urls:
        return 0

    final_url = parse.unquote(config["final_url"])

    # --- Step 2: Check each URL for a match ---
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        parsed = parse.urlparse(current_url)
        current_path = parsed.path

        # --- Step 3: Compare path/URL ---
        if final_url.startswith("http"):
            canonical_final = parse.urlunparse(parse.urlparse(final_url))
            if current_url != canonical_final:
                continue
        elif final_url.startswith("/"):
            final_parsed = parse.urlparse(final_url)
            if current_path != final_parsed.path:
                continue
        else:
            if final_url not in current_path:
                continue

        # --- Step 4: If config has a "query" field, check each key=value is in the URL ---
        if "query" in config and config["query"]:
            url_query = parse.unquote(parsed.query)
            query_params = parse.parse_qs(parsed.query)
            sysparm_query = parse.unquote(query_params.get("sysparm_query", [""])[0])
            sysparm_query = sysparm_query.removesuffix("^EQ")
            sysparm_query = sysparm_query.replace("^EQ", "^")

            query_matched = True
            for part in config["query"].split("^"):
                part = part.strip()
                if not part:
                    continue
                if part not in url_query and part not in sysparm_query:
                    query_matched = False
                    break

            if not query_matched:
                continue

        return 1

    return 0
Task: workarena.servicenow.all-menu
Goal: Navigate to the "Standard Change > Open Proposals" module of the "Change" application.
Task configuration: final_url: /now/nav/ui/classic/params/target/std_change_proposal_list.do
query: state=2

Validation code:
import re
import urllib.parse as parse
from typing import Any, Dict, List


def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:
    """
    Extract URLs from the last message and compare them to config["final_url"].
    Returns 1 if any URL matches (after unquoting and canonical parse/unparse), else 0.
    """
    if "final_url" not in config:
        raise ValueError("Target must contain 'final_url'")
    if not messages:
        raise ValueError("Messages must not be empty")

    content = messages[-1].get("content", "")

    # --- Step 1: Extract all URLs — markdown links first, then bare URLs ---
    seen = set()
    urls = []
    for m in re.finditer(r"\[.*?\]\((https?://[^)]+)\)", content):
        url = m.group(1)
        if url not in seen:
            seen.add(url)
            urls.append(url)
    for m in re.finditer(r"https?://\S+", content):
        url = m.group(0).rstrip(")].,")
        if url not in seen:
            seen.add(url)
            urls.append(url)

    if not urls:
        return 0

    final_url = parse.unquote(config["final_url"])

    # --- Step 2: Check each URL for a match ---
    for raw_url in urls:
        current_url = parse.urlunparse(parse.urlparse(parse.unquote(raw_url)))
        parsed = parse.urlparse(current_url)
        current_path = parsed.path

        # --- Step 3: Compare path/URL ---
        if final_url.startswith("http"):
            canonical_final = parse.urlunparse(parse.urlparse(final_url))
            if current_url != canonical_final:
                continue
        elif final_url.startswith("/"):
            final_parsed = parse.urlparse(final_url)
            if current_path != final_parsed.path:
                continue
        else:
            if final_url not in current_path:
                continue

        # --- Step 4: If config has a "query" field, check each key=value is in the URL ---
        if "query" in config and config["query"]:
            url_query = parse.unquote(parsed.query)
            query_params = parse.parse_qs(parsed.query)
            sysparm_query = parse.unquote(query_params.get("sysparm_query", [""])[0])
            sysparm_query = sysparm_query.removesuffix("^EQ")
            sysparm_query = sysparm_query.replace("^EQ", "^")

            query_matched = True
            for part in config["query"].split("^"):
                part = part.strip()
                if not part:
                    continue
                if part not in url_query and part not in sysparm_query:
                    query_matched = False
                    break

            if not query_matched:
                continue

        return 1

    return 0