feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
This commit is contained in:
230
services/monitor-agent-service/app/monitor_logger.py
Normal file
230
services/monitor-agent-service/app/monitor_logger.py
Normal file
@@ -0,0 +1,230 @@
|
||||
"""
|
||||
Monitor Agent Logger - Автоматичне створення MD файлів та Jupyter Notebook
|
||||
для кожного Monitor Agent з усіма змінами та подіями
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Базовий шлях для збереження логів Monitor Agent
|
||||
MONITOR_LOGS_DIR = Path(os.getenv("MONITOR_LOGS_DIR", "docs/monitor_agents"))
|
||||
MONITOR_LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def get_monitor_agent_file_paths(agent_id: str) -> Dict[str, Path]:
|
||||
"""
|
||||
Отримати шляхи до MD файлу та Jupyter Notebook для Monitor Agent
|
||||
|
||||
Args:
|
||||
agent_id: ID Monitor Agent (monitor, monitor-node-{node_id}, monitor-microdao-{microdao_id})
|
||||
|
||||
Returns:
|
||||
Dict з шляхами до MD файлу та Jupyter Notebook
|
||||
"""
|
||||
# Нормалізуємо agent_id для використання в іменах файлів
|
||||
safe_id = agent_id.replace('/', '_').replace(':', '_')
|
||||
|
||||
return {
|
||||
'md': MONITOR_LOGS_DIR / f"{safe_id}_changes.md",
|
||||
'ipynb': MONITOR_LOGS_DIR / f"{safe_id}_changes.ipynb",
|
||||
}
|
||||
|
||||
|
||||
def append_to_markdown(agent_id: str, change: Dict[str, Any], message: str) -> None:
|
||||
"""
|
||||
Додати зміну до MD файлу Monitor Agent
|
||||
|
||||
Args:
|
||||
agent_id: ID Monitor Agent
|
||||
change: Дані про зміну
|
||||
message: Повідомлення від Monitor Agent
|
||||
"""
|
||||
try:
|
||||
file_paths = get_monitor_agent_file_paths(agent_id)
|
||||
md_path = file_paths['md']
|
||||
|
||||
# Створюємо файл, якщо його немає
|
||||
if not md_path.exists():
|
||||
md_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(md_path, 'w', encoding='utf-8') as f:
|
||||
f.write(f"# 📊 Monitor Agent: {agent_id}\n\n")
|
||||
f.write(f"**Автоматично створено:** {datetime.now().isoformat()}\n\n")
|
||||
f.write("---\n\n")
|
||||
f.write("## 📝 Історія змін\n\n")
|
||||
|
||||
# Додаємо нову зміну
|
||||
timestamp = change.get('timestamp', datetime.now().isoformat())
|
||||
change_type = change.get('type', 'unknown')
|
||||
change_action = change.get('action', 'unknown')
|
||||
path = change.get('path', 'unknown')
|
||||
description = change.get('description', '')
|
||||
|
||||
with open(md_path, 'a', encoding='utf-8') as f:
|
||||
f.write(f"### {timestamp}\n\n")
|
||||
f.write(f"**Тип:** {change_type} | **Дія:** {change_action}\n\n")
|
||||
f.write(f"**Шлях:** `{path}`\n\n")
|
||||
if description:
|
||||
f.write(f"**Опис:** {description}\n\n")
|
||||
f.write(f"**Повідомлення від Monitor Agent:**\n\n")
|
||||
f.write(f"{message}\n\n")
|
||||
f.write("---\n\n")
|
||||
|
||||
logger.debug(f"✅ Appended change to {md_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error appending to markdown: {e}")
|
||||
|
||||
|
||||
def append_to_notebook(agent_id: str, change: Dict[str, Any], message: str) -> None:
|
||||
"""
|
||||
Додати зміну до Jupyter Notebook Monitor Agent
|
||||
|
||||
Args:
|
||||
agent_id: ID Monitor Agent
|
||||
change: Дані про зміну
|
||||
message: Повідомлення від Monitor Agent
|
||||
"""
|
||||
try:
|
||||
file_paths = get_monitor_agent_file_paths(agent_id)
|
||||
ipynb_path = file_paths['ipynb']
|
||||
|
||||
# Створюємо notebook, якщо його немає
|
||||
if not ipynb_path.exists():
|
||||
notebook = {
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
f"# 📊 Monitor Agent: {agent_id}\n",
|
||||
f"\n",
|
||||
f"**Автоматично створено:** {datetime.now().isoformat()}\n",
|
||||
f"\n",
|
||||
f"---\n",
|
||||
f"\n",
|
||||
f"## 📝 Історія змін\n",
|
||||
f"\n",
|
||||
f"Цей notebook автоматично оновлюється при кожній зміні в проєкті."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
else:
|
||||
# Читаємо існуючий notebook
|
||||
with open(ipynb_path, 'r', encoding='utf-8') as f:
|
||||
notebook = json.load(f)
|
||||
|
||||
# Додаємо нову комірку зі зміною
|
||||
timestamp = change.get('timestamp', datetime.now().isoformat())
|
||||
change_type = change.get('type', 'unknown')
|
||||
change_action = change.get('action', 'unknown')
|
||||
path = change.get('path', 'unknown')
|
||||
description = change.get('description', '')
|
||||
|
||||
new_cell = {
|
||||
"cell_type": "markdown",
|
||||
"metadata": {
|
||||
"timestamp": timestamp,
|
||||
"change_type": change_type,
|
||||
"change_action": change_action,
|
||||
"path": path
|
||||
},
|
||||
"source": [
|
||||
f"### {timestamp}\n",
|
||||
f"\n",
|
||||
f"**Тип:** {change_type} | **Дія:** {change_action}\n",
|
||||
f"\n",
|
||||
f"**Шлях:** `{path}`\n",
|
||||
f"\n",
|
||||
f"**Опис:** {description}\n",
|
||||
f"\n",
|
||||
f"**Повідомлення від Monitor Agent:**\n",
|
||||
f"\n",
|
||||
f"{message}\n",
|
||||
f"\n",
|
||||
f"---\n"
|
||||
]
|
||||
}
|
||||
|
||||
# Додаємо комірку на початок (нові зміни зверху)
|
||||
notebook['cells'].insert(1, new_cell)
|
||||
|
||||
# Зберігаємо максимум 200 комірок (крім першої заголовної)
|
||||
if len(notebook['cells']) > 201:
|
||||
notebook['cells'] = [notebook['cells'][0]] + notebook['cells'][1:201]
|
||||
|
||||
# Зберігаємо notebook
|
||||
with open(ipynb_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(notebook, f, indent=2, ensure_ascii=False)
|
||||
|
||||
logger.debug(f"✅ Appended change to {ipynb_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error appending to notebook: {e}")
|
||||
|
||||
|
||||
def log_monitor_change(agent_id: str, change: Dict[str, Any], message: str) -> None:
|
||||
"""
|
||||
Зберегти зміну в MD файл та Jupyter Notebook для Monitor Agent
|
||||
|
||||
Args:
|
||||
agent_id: ID Monitor Agent
|
||||
change: Дані про зміну
|
||||
message: Повідомлення від Monitor Agent
|
||||
"""
|
||||
try:
|
||||
# Додаємо до MD файлу
|
||||
append_to_markdown(agent_id, change, message)
|
||||
|
||||
# Додаємо до Jupyter Notebook
|
||||
append_to_notebook(agent_id, change, message)
|
||||
|
||||
logger.info(f"✅ Logged change to files for {agent_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error logging change: {e}")
|
||||
|
||||
|
||||
def get_monitor_agent_file_urls(agent_id: str, base_url: str = "/") -> Dict[str, str]:
|
||||
"""
|
||||
Отримати URL для MD файлу та Jupyter Notebook Monitor Agent
|
||||
|
||||
Args:
|
||||
agent_id: ID Monitor Agent
|
||||
base_url: Базовий URL для посилань
|
||||
|
||||
Returns:
|
||||
Dict з URL до MD файлу та Jupyter Notebook
|
||||
"""
|
||||
file_paths = get_monitor_agent_file_paths(agent_id)
|
||||
|
||||
# Конвертуємо абсолютні шляхи в відносні від кореня проєкту
|
||||
project_root = Path.cwd()
|
||||
|
||||
md_relative = file_paths['md'].relative_to(project_root) if file_paths['md'].is_relative_to(project_root) else file_paths['md']
|
||||
ipynb_relative = file_paths['ipynb'].relative_to(project_root) if file_paths['ipynb'].is_relative_to(project_root) else file_paths['ipynb']
|
||||
|
||||
return {
|
||||
'md': f"{base_url}{md_relative.as_posix()}",
|
||||
'ipynb': f"{base_url}{ipynb_relative.as_posix()}",
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user