diff --git a/services/comms/atlasbot-deployment.yaml b/services/comms/atlasbot-deployment.yaml index c2bc108..7a258ac 100644 --- a/services/comms/atlasbot-deployment.yaml +++ b/services/comms/atlasbot-deployment.yaml @@ -16,7 +16,7 @@ spec: labels: app: atlasbot annotations: - checksum/atlasbot-configmap: manual-atlasbot-5 + checksum/atlasbot-configmap: manual-atlasbot-6 vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "comms" vault.hashicorp.com/agent-inject-secret-turn-secret: "kv/data/atlas/comms/turn-shared-secret" diff --git a/services/comms/scripts/atlasbot/bot.py b/services/comms/scripts/atlasbot/bot.py index 3da93ba..69c1b84 100644 --- a/services/comms/scripts/atlasbot/bot.py +++ b/services/comms/scripts/atlasbot/bot.py @@ -444,6 +444,28 @@ def vm_cluster_snapshot() -> str: parts.append(pr) return "\n".join(parts).strip() +def nodes_summary(cluster_name: str) -> str: + try: + data = k8s_get("/api/v1/nodes?limit=500") + except Exception: + return "" + items = data.get("items") or [] + if not isinstance(items, list) or not items: + return "" + total = len(items) + ready = 0 + for node in items: + conditions = node.get("status", {}).get("conditions") or [] + for cond in conditions if isinstance(conditions, list) else []: + if cond.get("type") == "Ready": + if cond.get("status") == "True": + ready += 1 + break + not_ready = max(total - ready, 0) + if not_ready: + return f"{cluster_name} cluster has {total} nodes: {ready} Ready, {not_ready} NotReady." + return f"{cluster_name} cluster has {total} nodes, all Ready." + def _strip_code_fence(text: str) -> str: cleaned = (text or "").strip() match = CODE_FENCE_RE.match(cleaned) @@ -526,7 +548,8 @@ def ollama_reply(hist_key, prompt: str, *, context: str) -> str: "System: You are Atlas, the Titan lab assistant for Atlas/Othrys. " "Be helpful, direct, and concise. " "Prefer answering with exact repo paths and Kubernetes resource names. " - "Never include or request secret values." + "Never include or request secret values. " + "Respond in plain sentences; do not return JSON or code fences unless explicitly asked." ) transcript_parts = [system] if context: @@ -601,6 +624,14 @@ def sync_loop(token: str, room_id: str): if not (is_dm or mentioned): continue + lower_body = body.lower() + if re.search(r"\\bhow many nodes\\b|\\bnode count\\b|\\bnumber of nodes\\b", lower_body): + if any(word in lower_body for word in ("cluster", "atlas", "titan")): + summary = nodes_summary("Atlas") + if summary: + send_msg(token, rid, summary) + continue + # Only do live cluster/metrics introspection in DMs. allow_tools = is_dm