public/maester/aiagent/Test-MtAIAgentMissingInstructions.ps1

<#
.SYNOPSIS
    Tests if AI agents with generative orchestration have custom instructions.

.DESCRIPTION
    Checks all Copilot Studio agents that use generative orchestration (generative
    actions enabled) for the presence of custom instructions. Agents without
    instructions rely entirely on the LLM's default behavior, which increases the
    risk of prompt injection, off-topic responses, and uncontrolled tool usage.

.OUTPUTS
    [bool] - Returns $true if all generative agents have instructions, $false if
    any generative agent is missing instructions, $null if data is unavailable.

.EXAMPLE
    Test-MtAIAgentMissingInstructions

.LINK
    https://maester.dev/docs/commands/Test-MtAIAgentMissingInstructions
#>


function Test-MtAIAgentMissingInstructions {
    [CmdletBinding()]
    [OutputType([bool])]
    param()

    $agents = Get-MtAIAgentInfo
    if ($null -eq $agents) {
        Add-MtTestResultDetail -SkippedBecause 'Custom' -SkippedCustomReason 'No Copilot Studio agent data available. Ensure DataverseEnvironmentUrl is configured in maester-config.json and Connect-Maester -Service Dataverse has been run. See https://maester.dev/docs/tests/MT.1121 for prerequisites.'
        return $null
    }

    $failedAgents = @()

    foreach ($agent in $agents) {
        # Check if generative orchestration is enabled using the dedicated column
        $generativeEnabled = $false
        if ($agent.PSObject.Properties['IsGenerativeOrchestrationEnabled']) {
            $generativeEnabled = $agent.IsGenerativeOrchestrationEnabled -eq $true -or $agent.IsGenerativeOrchestrationEnabled -eq 'true'
        }

        if (-not $generativeEnabled) {
            continue
        }

        # Check for custom instructions in RawAgentInfo
        $hasInstructions = $false
        if (-not [string]::IsNullOrEmpty($agent.RawAgentInfo)) {
            $rawInfo = $null
            try {
                if ($agent.RawAgentInfo -is [string]) {
                    $rawInfo = $agent.RawAgentInfo | ConvertFrom-Json -ErrorAction Stop
                } else {
                    $rawInfo = $agent.RawAgentInfo
                }
            } catch {
                Write-Verbose "Could not parse RawAgentInfo for agent $($agent.AIAgentName): $_"
            }

            if ($null -ne $rawInfo) {
                foreach ($prop in @('Instructions', 'CustomInstructions', 'SystemPrompt', 'SystemMessage')) {
                    if ($rawInfo.PSObject.Properties[$prop] -and -not [string]::IsNullOrWhiteSpace($rawInfo.$prop)) {
                        $hasInstructions = $true
                        break
                    }
                }
            }
        }

        if (-not $hasInstructions) {
            $failedAgents += [PSCustomObject]@{
                AIAgentName   = $agent.AIAgentName
                EnvironmentId = $agent.EnvironmentId
                AgentStatus   = $agent.AgentStatus
            }
        }
    }

    if ($failedAgents.Count -eq 0) {
        $testResultMarkdown = "Well done. All AI agents with generative orchestration have custom instructions."
    } else {
        $testResultMarkdown = "Found $($failedAgents.Count) AI agent(s) using generative orchestration without custom instructions.`n`n%TestResult%"
        $result = "| Agent Name | Environment | Status |`n"
        $result += "| --- | --- | --- |`n"
        foreach ($agent in $failedAgents) {
            $result += "| $($agent.AIAgentName) | $($agent.EnvironmentId) | $($agent.AgentStatus) |`n"
        }
        $testResultMarkdown = $testResultMarkdown -replace "%TestResult%", $result
    }

    Add-MtTestResultDetail -Result $testResultMarkdown -Severity "Medium"
    return ($failedAgents.Count -eq 0)
}