diff --git a/HOST-REBOOT-AI-AUTOMATION-SOP.md b/HOST-REBOOT-AI-AUTOMATION-SOP.md index e527d294e..3afd27070 100644 --- a/HOST-REBOOT-AI-AUTOMATION-SOP.md +++ b/HOST-REBOOT-AI-AUTOMATION-SOP.md @@ -423,6 +423,40 @@ Recovery is complete only when all of the following are captured: - Send user-facing Telegram only for warnings, criticals, unresolved blockers, failed/escalated operator commands, and controlled-remediation actions. - Keep successful automation in evidence, problem counters, KM, and RAG so repeated incidents trend down without flooding the SRE group. +## Agent99 Telegram Alert UX And Noise Freeze Work Items + +2026-07-10 10:41 Asia/Taipei: + +- Operator requirement: + - AwoooI SRE Telegram alerts must be human-readable, professionally formatted, and action-oriented. + - Important incident updates should include visual context when available: dashboard image, public-route screenshot, service card, or evidence summary image. + - Do not send raw machine fields, empty metrics, repeated success receipts, or text-only alert floods. +- Immediate runtime noise freeze applied on 99: + - Disabled scheduled task: `Wooo-Agent99-Performance-Guard`. + - Disabled scheduled task: `Wooo-Agent99-Backup-Health`. + - Disabled scheduled task: `Wooo-Agent99-Self-Health`. + - Ended running task instance: `Wooo-Agent99-Performance-Guard`. + - Kept alert intake active: `Wooo-Agent99-SRE-Alert-Relay`, `Wooo-Agent99-SRE-Alert-Inbox`, `Wooo-Agent99-Telegram-Inbox`, and `Wooo-Agent99-Control-Loop`. +- 2026-07-10 10:47 runtime readback: + - `Wooo-Agent99-Performance-Guard`: `Disabled`, last result `0`. + - `Wooo-Agent99-Backup-Health`: `Disabled`, last result `0`. + - `Wooo-Agent99-Self-Health`: `Disabled`, last result `0`. + - `Wooo-Agent99-SRE-Alert-Relay`: `Running`. + - `Wooo-Agent99-SRE-Alert-Inbox`: `Ready`, last result `0`. + - `Wooo-Agent99-Telegram-Inbox`: `Running`. + - `Wooo-Agent99-Control-Loop`: `Ready`, last result `0`. + - `SecurityTriage` smoke evidence: `C:\Wooo\Agent99\evidence\agent99-SecurityTriage-20260710-104456.json`. + - `SecurityTriage` smoke result: `security_triage_no_recent_alert_body_seen`, `hitCount=0`, `telegramCount=0`. +- P0 work items added: + - P0-0: keep noisy sensor schedules paused until their alerts have dedupe, clear reason text, and verifier-backed fields. + - P0-1: replace raw Agent99 text alerts with professional Telegram incident cards: title, severity, affected service, user impact, Agent99 action, verifier result, evidence path, repeated count, next action. + - P0-2: add visual attachment support for high-impact alerts: public URL screenshots, dashboard screenshots, and rendered evidence summary images. + - P0-3: suppress alerts with empty metric fields such as blank load/core, memory, disk, age, or maxAge unless a verifier explains why readback failed. + - P0-4: route application config alerts such as Bitan `E_AI_MISSING_KEY` to evidence-first `Status`; do not read or print secret values. + - P0-5: route IWOOOS/Wazuh/SIEM alerts to `SecurityTriage`; collect source log reference, affected host/service, blast radius, repeated count, and allowlisted next action without arbitrary shell or secret exposure. + - P0-6: route Gitea repository missing alerts to evidence-first `Status` until a dedicated Gitea recovery playbook is verified. + - P0-7: run live TG-to-Agent99 end-to-end tests for each alert family before re-enabling the paused sensor schedules. + ## Agent99 Monitoring Alert Routing Self-Test Receipt 2026-07-10 10:17 Asia/Taipei: @@ -437,6 +471,13 @@ Recovery is complete only when all of the following are captured: - `caseCount=9`. - `passedCount=9`. - `failedCount=0`. +- Expanded self-test evidence: + - `C:\Wooo\Agent99\evidence\agent99-SreAlertRoutingSelfTest-20260710-104455.json`. + - `ok=True`. + - `caseCount=12`. + - `passedCount=12`. + - `failedCount=0`. + - Added routes: IWOOOS/Wazuh/SIEM security alert -> `SecurityTriage`; Gitea repository missing -> `Status`; Bitan AI support missing key / locale guard -> `Status`. - Covered alert routes: - Public route 502 -> `Recover`, `controlledApply=True`. - Host down / reboot / VM not running -> `Recover`, `controlledApply=True`. diff --git a/agent99-control-plane.ps1 b/agent99-control-plane.ps1 index 8f7c856ea..5e34b2bb2 100644 --- a/agent99-control-plane.ps1 +++ b/agent99-control-plane.ps1 @@ -1,5 +1,5 @@ param( - [ValidateSet("Status", "Recover", "StartVMs", "PublicSmoke", "HarborRepair", "AwoooRepair", "Perf", "LoadShed", "SelfCheck", "BackupCheck", "ProviderFreshness", "ControlTick")] + [ValidateSet("Status", "Recover", "StartVMs", "PublicSmoke", "HarborRepair", "AwoooRepair", "Perf", "LoadShed", "SelfCheck", "BackupCheck", "ProviderFreshness", "SecurityTriage", "ControlTick")] [string]$Mode = "Status", [switch]$ControlledApply, [string]$ConfigPath = "C:\Wooo\Agent99\config\agent99.config.json", @@ -221,6 +221,18 @@ function Format-AgentTelegramText { $lines += "Event: AI service health" $lines += "Service: $serviceName" $lines += "Action: inspect health endpoint and service/container state before controlled repair." + } elseif ($EventType -eq "security_triage") { + $status = if ($Data -and $Data.PSObject.Properties["status"]) { $Data.status } else { "unknown" } + $hitCount = if ($Data -and $Data.PSObject.Properties["hitCount"]) { $Data.hitCount } else { 0 } + $evidencePath = if ($Data -and $Data.PSObject.Properties["evidencePath"]) { $Data.evidencePath } else { $null } + $lines += "Event: security triage" + $lines += "Status: $status" + $lines += "Evidence hits: $hitCount" + $lines += "Action: classify blast radius, affected host/service, source log reference, repeated count, and allowlisted next action." + $lines += "Guard: no arbitrary shell, no data deletion, no disabling controls, no secret exposure." + if ($evidencePath) { + $lines += "Evidence: $evidencePath" + } } elseif ($EventType -eq "agent_selfcheck") { $lines += "Event: Agent99 self health" $lines += "Summary: $Message" @@ -1889,7 +1901,7 @@ function Invoke-AgentQueuedCommands { $failedDir = Join-Path $queueDir "failed" New-Item -ItemType Directory -Force -Path $queueDir, $processedDir, $failedDir | Out-Null - $allowedModes = @("Status", "Recover", "StartVMs", "PublicSmoke", "HarborRepair", "AwoooRepair", "Perf", "LoadShed", "SelfCheck", "BackupCheck", "ProviderFreshness") + $allowedModes = @("Status", "Recover", "StartVMs", "PublicSmoke", "HarborRepair", "AwoooRepair", "Perf", "LoadShed", "SelfCheck", "BackupCheck", "ProviderFreshness", "SecurityTriage") $commands = @(Get-ChildItem -Path $queueDir -Filter "*.json" -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -notmatch "^(processed|failed|running)-" } | Sort-Object LastWriteTime | @@ -2009,6 +2021,88 @@ function Invoke-AgentControlTick { $result } +function Invoke-AgentSecurityTriage { + $lookbackMinutes = 120 + $since = (Get-Date).AddMinutes(-1 * $lookbackMinutes) + $triageId = "security-triage-" + (Get-Date -Format "yyyyMMdd-HHmmss") + $triageDir = Join-Path $Config.agentRoot "state\security-triage" + New-Item -ItemType Directory -Force -Path $triageDir | Out-Null + $triagePath = Join-Path $triageDir "$triageId.json" + + $patterns = @( + "wazuh", + "siem", + "security", + "auth_failed", + "bruteforce", + "brute force", + "intrusion", + "malware", + "privilege", + "vulnerability", + "cve", + "ioc", + "suspicious", + "unauthorized" + ) + $scanDirs = @( + (Join-Path $Config.agentRoot "alerts\incoming"), + (Join-Path $Config.agentRoot "alerts\processed"), + (Join-Path $Config.agentRoot "queue"), + (Join-Path $Config.agentRoot "queue\processed"), + (Join-Path $Config.agentRoot "requests") + ) | Where-Object { Test-Path $_ } | Select-Object -Unique + + $hits = @() + foreach ($dir in $scanDirs) { + $files = @(Get-ChildItem -LiteralPath $dir -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTime -ge $since -and $_.Length -lt 2097152 }) + foreach ($file in $files) { + $matchedPatterns = @() + foreach ($pattern in $patterns) { + if (Select-String -LiteralPath $file.FullName -Pattern $pattern -SimpleMatch -Quiet -ErrorAction SilentlyContinue) { + $matchedPatterns += $pattern + } + } + if (@($matchedPatterns).Count -gt 0) { + $excerpt = "" + $firstHit = Select-String -LiteralPath $file.FullName -Pattern $matchedPatterns[0] -SimpleMatch -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($firstHit) { + $excerpt = [string]$firstHit.Line + $excerpt = $excerpt -replace '("?(telegram|token|chat|authorization|password|secret|cookie)[^,}\r\n]*"?\s*:\s*)"[^"]+"', '$1"[redacted]"' + if ($excerpt.Length -gt 500) { $excerpt = $excerpt.Substring(0, 500) + "...(truncated)" } + } + $hits += [pscustomobject]@{ + path = $file.FullName + lastWrite = $file.LastWriteTime.ToString("o") + patterns = @($matchedPatterns) + excerpt = $excerpt + } + } + } + } + + $status = if (@($hits).Count -gt 0) { "security_triage_candidate_created" } else { "security_triage_no_recent_alert_body_seen" } + $triage = [pscustomobject]@{ + timestamp = (Get-Date -Format o) + triageId = $triageId + status = $status + lookbackMinutes = $lookbackMinutes + since = $since.ToString("o") + hitCount = @($hits).Count + hits = @($hits | Sort-Object lastWrite -Descending | Select-Object -First 20) + requiredReadback = @("source_log_reference", "affected_host_or_service", "blast_radius", "repeated_count", "allowlisted_next_action") + allowedActions = @("classify", "read_existing_evidence", "write_km", "request_source_log_reference", "recommend_allowlisted_next_action") + prohibitedActions = @("arbitrary_shell", "data_delete", "disable_security_control", "secret_exposure") + evidencePath = $triagePath + } + $triage | ConvertTo-Json -Depth 10 | Set-Content -Path $triagePath -Encoding UTF8 + Write-AgentLog "security_triage triage=$triageId hits=$(@($hits).Count) status=$status path=$triagePath" + $eventSeverity = if (@($hits).Count -gt 0) { "warning" } else { "info" } + Record-AgentEvent "security_triage" $eventSeverity "status=$status hits=$(@($hits).Count)" $triage -Alert + $triage +} + function Invoke-AgentProviderFreshnessDirectReadback { param( [int]$LookbackMinutes @@ -2446,6 +2540,7 @@ $loadShedding = @() $selfHealth = $null $backupHealth = $null $providerFreshness = $null +$securityTriage = $null $controlTick = $null if ($Mode -in @("Recover", "StartVMs")) { @@ -2513,6 +2608,10 @@ if ($Mode -in @("ProviderFreshness")) { $providerFreshness = Invoke-AgentProviderFreshnessTriage } +if ($Mode -in @("SecurityTriage")) { + $securityTriage = Invoke-AgentSecurityTriage +} + if ($Mode -in @("ControlTick")) { $controlTick = Invoke-AgentControlTick } @@ -2526,8 +2625,9 @@ $selfHealthWarning = if ($selfHealth -and $selfHealth.severity -eq "warning") { $backupCritical = if ($backupHealth -and $backupHealth.severity -eq "critical") { 1 } else { 0 } $backupWarning = if ($backupHealth -and $backupHealth.severity -eq "warning") { 1 } else { 0 } $providerFreshnessWarning = if ($providerFreshness) { 1 } else { 0 } -$summarySeverity = if ($publicFailures -gt 0 -or $aiFailures -gt 0 -or $perfCritical -gt 0 -or $selfHealthCritical -gt 0 -or $backupCritical -gt 0) { "critical" } elseif ($perfWarning -gt 0 -or $selfHealthWarning -gt 0 -or $backupWarning -gt 0 -or $providerFreshnessWarning -gt 0) { "warning" } else { "info" } -Record-AgentEvent "agent_complete" $summarySeverity "mode=$Mode publicFailures=$publicFailures aiFailures=$aiFailures perfCritical=$perfCritical perfWarning=$perfWarning selfHealth=$($selfHealth.severity) backupHealth=$($backupHealth.severity) providerFreshness=$($providerFreshness.status) evidence=$jsonPath" $null -NoAlert +$securityTriageWarning = if ($securityTriage -and $securityTriage.hitCount -gt 0) { 1 } else { 0 } +$summarySeverity = if ($publicFailures -gt 0 -or $aiFailures -gt 0 -or $perfCritical -gt 0 -or $selfHealthCritical -gt 0 -or $backupCritical -gt 0) { "critical" } elseif ($perfWarning -gt 0 -or $selfHealthWarning -gt 0 -or $backupWarning -gt 0 -or $providerFreshnessWarning -gt 0 -or $securityTriageWarning -gt 0) { "warning" } else { "info" } +Record-AgentEvent "agent_complete" $summarySeverity "mode=$Mode publicFailures=$publicFailures aiFailures=$aiFailures perfCritical=$perfCritical perfWarning=$perfWarning selfHealth=$($selfHealth.severity) backupHealth=$($backupHealth.severity) providerFreshness=$($providerFreshness.status) securityTriage=$($securityTriage.status) evidence=$jsonPath" $null -NoAlert $result = [pscustomobject]@{ timestamp = (Get-Date -Format o) @@ -2544,6 +2644,7 @@ $result = [pscustomobject]@{ selfHealth = $selfHealth backupHealth = $backupHealth providerFreshness = $providerFreshness + securityTriage = $securityTriage controlTick = $controlTick events = $script:Events telegram = $script:TelegramAttempts diff --git a/agent99-sre-alert-inbox.ps1 b/agent99-sre-alert-inbox.ps1 index fff648eb7..f69f5abe6 100644 --- a/agent99-sre-alert-inbox.ps1 +++ b/agent99-sre-alert-inbox.ps1 @@ -19,7 +19,7 @@ $RequestDir = Join-Path $AgentRoot "requests" New-Item -ItemType Directory -Force -Path $EvidenceDir, $IncomingDir, $RunningDir, $ProcessedDir, $FailedDir, $IgnoredDir, $QueueDir, $RequestDir | Out-Null -$allowedModes = @("Status", "Recover", "StartVMs", "PublicSmoke", "HarborRepair", "AwoooRepair", "Perf", "LoadShed", "SelfCheck", "BackupCheck", "ProviderFreshness") +$allowedModes = @("Status", "Recover", "StartVMs", "PublicSmoke", "HarborRepair", "AwoooRepair", "Perf", "LoadShed", "SelfCheck", "BackupCheck", "ProviderFreshness", "SecurityTriage") $remediationModes = @("Recover", "StartVMs", "HarborRepair", "AwoooRepair", "Perf", "LoadShed") function Get-AgentField { @@ -71,7 +71,7 @@ function Test-AgentAlertActionable { if ($severity -in @("critical", "warning", "error", "degraded", "down", "failed")) { return $true } - if ($text -match "502|provider_freshness_signal|source_provider_freshness_triage|raw_signal_normalized|controlled_runtime_candidate|freshness|last_seen|last seen|ingestion|imagepullbackoff|errimagepull|crashloopbackoff|host_down|vm_not_running|backup_failed|backup_missing|disk_used|load_per_core|cpu|memory|harbor|registry|k3s") { + if ($text -match "502|provider_freshness_signal|source_provider_freshness_triage|raw_signal_normalized|controlled_runtime_candidate|freshness|last_seen|last seen|ingestion|imagepullbackoff|errimagepull|crashloopbackoff|host_down|vm_not_running|vm_stuck|backup_failed|backup_missing|disk_used|load_per_core|cpu|memory|harbor|registry|k3s|wazuh|siem|security|intrusion|malware|auth_failed|bruteforce|cve|ioc|gitea|repo_missing|repository_missing|missing_key|e_ai_missing_key|application_ai_config_error|locale guard|ai support") { return $true } return $false @@ -95,6 +95,9 @@ function Resolve-AgentAlertMode { if ($text -match "backup|snapshot|restore drill|cron") { return "BackupCheck" } + if ($text -match "wazuh|siem|security|intrusion|malware|auth_failed|bruteforce|privilege|vulnerability|cve|ioc|suspicious|unauthorized") { + return "SecurityTriage" + } if ($text -match "provider_freshness_signal|source_provider_freshness_triage|raw_signal_normalized|controlled_runtime_candidate|last_seen|last seen|provider freshness|provider window|ingestion") { return "ProviderFreshness" } @@ -104,6 +107,12 @@ function Resolve-AgentAlertMode { if ($text -match "awoooi|k3s|imagepullbackoff|errimagepull|crashloopbackoff|pod|deployment") { return "AwoooRepair" } + if ($text -match "missing_key|e_ai_missing_key|application_ai_config_error|locale guard|ai support") { + return "Status" + } + if ($text -match "gitea|repo_missing|repository_missing|repo missing|repository missing") { + return "Status" + } if ($suggestedMode -and $suggestedMode -in $allowedModes) { return $suggestedMode } @@ -131,6 +140,9 @@ function New-AgentInstructionFromAlert { if ($kind -eq "provider_freshness_signal" -or $message.ToLowerInvariant() -match "provider_freshness_signal|source_provider_freshness_triage|raw_signal_normalized|controlled_runtime_candidate|provider freshness|provider window|last_seen|last seen|ingestion") { return "Provider freshness candidate required; alertId=$AlertId; source=$source; severity=$severity; service=$service; mode=$ModeName; require providerWindow,lastSeen,directReference,verifierReadback; mark missing,expired,timeout,no_false_green; do not switch provider, call paid model, edit env, or reload." } + if ($ModeName -eq "SecurityTriage" -or $kind -match "security|wazuh|siem") { + return "Security triage required; alertId=$AlertId; source=$source; severity=$severity; service=$service; mode=$ModeName; classify blast radius, affected host/service, source log reference, repeated count, and recommended allowlisted next action; do not run arbitrary shell, delete data, disable controls, or expose secrets." + } $parts = @("SRE alert", "id=$AlertId", "source=$source", "severity=$severity", "service=$service", "kind=$kind", "mode=$ModeName") if ($hostName) { $parts += "host=$hostName" } @@ -255,6 +267,51 @@ function Invoke-AgentAlertRoutingSelfTest { message = "provider_freshness_signal source_provider_freshness_triage raw_signal_normalized controlled_runtime_candidate" } }, + [pscustomobject]@{ + name = "security alert" + expectedActionable = $true + expectedMode = "SecurityTriage" + expectedControlledApply = $false + alert = [pscustomobject]@{ + id = "selftest-security" + source = "agent99-routing-selftest" + severity = "critical" + kind = "wazuh_security_alert" + service = "iwooos-security" + host = "192.168.0.112" + message = "Wazuh SIEM security alert auth_failed brute force suspicious login" + } + }, + [pscustomobject]@{ + name = "gitea repository missing" + expectedActionable = $true + expectedMode = "Status" + expectedControlledApply = $false + alert = [pscustomobject]@{ + id = "selftest-gitea-repo-missing" + source = "agent99-routing-selftest" + severity = "critical" + kind = "repository_missing" + service = "gitea" + host = "192.168.0.110" + message = "Gitea repo_missing repository missing for production project" + } + }, + [pscustomobject]@{ + name = "application ai config error" + expectedActionable = $true + expectedMode = "Status" + expectedControlledApply = $false + alert = [pscustomobject]@{ + id = "selftest-app-ai-config" + source = "agent99-routing-selftest" + severity = "warning" + kind = "application_ai_config_error" + service = "bitan-pharmacy" + host = "" + message = "AI support locale guard failed E_AI_MISSING_KEY missing_key" + } + }, [pscustomobject]@{ name = "unknown warning" expectedActionable = $true diff --git a/agent99-telegram-inbox.ps1 b/agent99-telegram-inbox.ps1 index a70f03aaa..2a56e810f 100644 --- a/agent99-telegram-inbox.ps1 +++ b/agent99-telegram-inbox.ps1 @@ -225,11 +225,11 @@ function Test-AgentMonitoringAlertText { return $false } - if ($lower -match "alertmanager|\bfiring\b|\bcritical\b|\bwarning\b|\bdegraded\b|\bdown\b|\b502\b|provider_freshness_signal|source_provider_freshness_triage|raw_signal_normalized|controlled_runtime_candidate|freshness|last_seen|last seen|ingestion|imagepullbackoff|errimagepull|crashloopbackoff|harbor|registry|k3s|backup|snapshot|cpu|load|memory|disk|hostdown|host_down|vm_not_running") { + if ($lower -match "alertmanager|\bfiring\b|\bcritical\b|\bwarning\b|\bdegraded\b|\bdown\b|\b502\b|provider_freshness_signal|source_provider_freshness_triage|raw_signal_normalized|controlled_runtime_candidate|freshness|last_seen|last seen|ingestion|imagepullbackoff|errimagepull|crashloopbackoff|harbor|registry|k3s|backup|snapshot|cpu|load|memory|disk|hostdown|host_down|vm_not_running|vm_stuck|vmware|wazuh|siem|security|intrusion|malware|auth_failed|bruteforce|cve|ioc|gitea|repo_missing|repository_missing|ai support|locale guard|missing_key|e_ai_missing_key") { return $true } - foreach ($code in @(0x544A, 0x8B66, 0x7570, 0x5E38, 0x56B4, 0x91CD, 0x8CA0, 0x8F09, 0x78C1, 0x789F, 0x5099, 0x4EFD)) { + foreach ($code in @(0x544A, 0x8B66, 0x7570, 0x5E38, 0x56B4, 0x91CD, 0x8CA0, 0x8F09, 0x78C1, 0x789F, 0x5099, 0x4EFD, 0x5BA2, 0x670D, 0x78A7, 0x6F6D)) { if ($trimmed.IndexOf([string][char]$code) -ge 0) { return $true } } @@ -259,15 +259,18 @@ function Get-AgentTelegramAlertSeverity { function Get-AgentTelegramAlertKind { param([string]$Text) $lower = $Text.ToLowerInvariant() - if ($lower -match "provider_freshness_signal|source_provider_freshness_triage|raw_signal_normalized|controlled_runtime_candidate|freshness|last_seen|last seen|ingestion") { return "provider_freshness_signal" } + if ($lower -match "backup|snapshot|cron" -or (Test-AgentTextContainsCode $Text @(0x5099, 0x4EFD))) { return "backup_health" } + if ($lower -match "wazuh|siem|security|intrusion|malware|auth_failed|bruteforce|cve|ioc") { return "wazuh_security_alert" } + if ($lower -match "gitea|repo_missing|repository_missing|repo missing|repository missing") { return "repository_missing" } + if ($lower -match "provider_freshness_signal|source_provider_freshness_triage|raw_signal_normalized|controlled_runtime_candidate|last_seen|last seen|provider freshness|provider window|ingestion") { return "provider_freshness_signal" } if ($lower -match "502|site_502|public_route|website|route") { return "public_route_502" } + if ($lower -match "missing_key|e_ai_missing_key|ai support|locale guard" -or (Test-AgentTextContainsCode $Text @(0x5BA2, 0x670D))) { return "application_ai_config_error" } if ($lower -match "cpu|load" -or (Test-AgentTextContainsCode $Text @(0x8CA0, 0x8F09))) { return "performance_cpu" } if ($lower -match "disk" -or (Test-AgentTextContainsCode $Text @(0x78C1, 0x789F))) { return "performance_disk" } if ($lower -match "memory|mem_|ram" -or (Test-AgentTextContainsCode $Text @(0x8A18, 0x61B6, 0x9AD4))) { return "performance_memory" } if ($lower -match "harbor|registry") { return "harbor_registry" } if ($lower -match "awoooi|k3s|imagepullbackoff|errimagepull|crashloopbackoff|pod|deployment") { return "awoooi_k3s" } - if ($lower -match "backup|snapshot|cron" -or (Test-AgentTextContainsCode $Text @(0x5099, 0x4EFD))) { return "backup_health" } - if ($lower -match "hostdown|host_down|vm_not_running|reboot|restart|down" -or (Test-AgentTextContainsCode $Text @(0x91CD, 0x555F, 0x95DC, 0x6A5F))) { return "host_recovery" } + if ($lower -match "hostdown|host_down|vm_not_running|vm_stuck|vmware|reboot|restart|down" -or (Test-AgentTextContainsCode $Text @(0x91CD, 0x555F, 0x95DC, 0x6A5F))) { return "host_recovery" } return "monitoring_alert" } @@ -276,8 +279,10 @@ function Get-AgentTelegramAlertService { $lower = $Text.ToLowerInvariant() if ($lower -match "stock") { return "stockplatform" } if ($lower -match "2026fifa|fifa") { return "2026fifa" } + if ($lower -match "bitan" -or (Test-AgentTextContainsCode $Text @(0x78A7, 0x6F6D))) { return "bitan-pharmacy" } if ($lower -match "gitea") { return "gitea" } if ($lower -match "harbor") { return "harbor" } + if ($lower -match "wazuh|siem|security") { return "iwooos-security" } if ($lower -match "agent99") { return "agent99" } if ($lower -match "awoooi|k3s|502|site|route|website") { return "awoooi" } return "unknown"