diff --git a/backend/frontend/index.html b/backend/frontend/index.html
index 1d7b195..157a264 100644
--- a/backend/frontend/index.html
+++ b/backend/frontend/index.html
@@ -522,7 +522,7 @@
const saved = localStorage.getItem('aoc_filters');
if (!saved && this.options.services.length) {
// Default: exclude noisy high-volume services
- const noisy = ['Exchange', 'SharePoint'];
+ const noisy = ['Exchange', 'SharePoint', 'Teams'];
this.filters.selectedServices = this.options.services.filter((s) => !noisy.includes(s));
} else if (saved) {
try {
@@ -617,7 +617,7 @@
},
clearFilters() {
- const noisy = ['Exchange', 'SharePoint'];
+ const noisy = ['Exchange', 'SharePoint', 'Teams'];
this.filters = { actor: '', selectedServices: this.options.services.filter((s) => !noisy.includes(s)), search: '', operation: '', result: '', start: '', end: '', limit: 100, includeTags: '', excludeTags: '' };
this.saveFilters();
this.resetPagination();
diff --git a/backend/routes/ask.py b/backend/routes/ask.py
index d855e39..23bab4a 100644
--- a/backend/routes/ask.py
+++ b/backend/routes/ask.py
@@ -1,3 +1,4 @@
+import asyncio
import json
import re
from datetime import UTC, datetime, timedelta
@@ -49,7 +50,7 @@ _SERVICE_INTENTS = {
# Services that are extremely noisy for typical admin questions.
# We exclude them by default on broad questions unless the user explicitly mentions them.
-_NOISY_SERVICES = {"Exchange", "SharePoint"}
+_NOISY_SERVICES = {"Exchange", "SharePoint", "Teams"}
# Services that are generally admin-relevant and kept by default.
_DEFAULT_ADMIN_SERVICES = {
@@ -471,11 +472,73 @@ Do not invent facts that are not in the data.
"""
+_GUID_RE = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
+
+
+def _extract_guids(obj: dict | list | str) -> set[str]:
+ """Recursively extract UUID-like strings from a JSON structure."""
+ guids = set()
+ if isinstance(obj, dict):
+ for k, v in obj.items():
+ if k.lower() in ("id", "groupid", "userid", "targetid") and isinstance(v, str) and _GUID_RE.match(v):
+ guids.add(v)
+ guids.update(_extract_guids(v))
+ elif isinstance(obj, list):
+ for item in obj:
+ guids.update(_extract_guids(item))
+ elif isinstance(obj, str) and _GUID_RE.match(obj):
+ guids.add(obj)
+ return guids
+
+
+async def _resolve_guids_for_event(event: dict) -> dict[str, str]:
+ """Try to resolve GUIDs in an event to human-readable names via Graph API."""
+ raw = event.get("raw") or {}
+ guids = _extract_guids(raw)
+ # Also include any GUIDs in targetResources that might not have displayName
+ for tr in raw.get("targetResources") or []:
+ tid = tr.get("id")
+ if tid and _GUID_RE.match(tid):
+ guids.add(tid)
+ for tr in raw.get("modifiedProperties") or []:
+ for key in ("oldValue", "newValue"):
+ val = tr.get(key)
+ if val and _GUID_RE.match(val):
+ guids.add(val)
+
+ if not guids:
+ return {}
+
+ try:
+ from graph.auth import get_access_token
+ from graph.resolve import resolve_directory_object
+
+ token = await asyncio.to_thread(get_access_token)
+ cache: dict[str, dict] = {}
+ resolved = {}
+ for gid in guids:
+ result = await asyncio.to_thread(resolve_directory_object, gid, token, cache)
+ if result:
+ resolved[gid] = result["name"]
+ return resolved
+ except Exception as exc:
+ logger.warning("GUID resolution failed", error=str(exc))
+ return {}
+
+
async def _explain_event(event: dict, related: list[dict]) -> str:
if not LLM_API_KEY:
raise RuntimeError("LLM_API_KEY not configured")
+ # Resolve GUIDs to names before sending to LLM
+ resolved = await _resolve_guids_for_event(event)
+
event_text = json.dumps(event, indent=2, default=str)
+ resolution_text = ""
+ if resolved:
+ resolution_text = "\nResolved GUIDs:\n"
+ for gid, name in resolved.items():
+ resolution_text += f" {gid} → {name}\n"
related_text = ""
if related:
@@ -492,7 +555,7 @@ async def _explain_event(event: dict, related: list[dict]) -> str:
{"role": "system", "content": _EXPLAIN_SYSTEM_PROMPT},
{
"role": "user",
- "content": f"Audit event:\n{event_text}{related_text}\n\nPlease explain this event.",
+ "content": f"Audit event:\n{event_text}{resolution_text}{related_text}\n\nPlease explain this event.",
},
]