| Server IP : 85.155.190.233 / Your IP : 216.73.216.103 Web Server : nginx/1.24.0 System : Linux antigravity-cli 6.8.0-31-generic #31-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 20 00:40:06 UTC 2024 x86_64 User : wp-moonbloom ( 1001) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /proc/thread-self/root/opt/Gemini_Swarm_Core/scripts/ |
Upload File : |
#!/usr/bin/env python3
"""
PostInvocation hook for agy schedule — sends agent output to Telegram
and logs the run to sessions.db (swarm_run_sessions table).
Works in two modes:
- Hook mode: agy pipes JSON to stdin after task completes
- Pipe mode: shell script pipes agy stdout as plain text (crontab)
"""
import json
import os
import sqlite3
import sys
import urllib.request
import re
from datetime import datetime, timezone
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "sessions.db")
TASK_NAME = os.environ.get("TREND_SCOUT_TASK", "trend_scout")
def clean_plain_text(text: str) -> str:
ansi_escape = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]')
lines = text.splitlines()
cleaned_lines = []
skip_prefixes = (
"warning:", "ripgrep", "mcp", "using a terminal",
"fallback", "falling back", "256-color", "calling tool",
"tool response", "[system]", "[error]", "[info]", "[warning]", "[debug]"
)
for line in lines:
clean_line = ansi_escape.sub("", line).strip()
clean_line_lower = clean_line.lower()
if not clean_line:
cleaned_lines.append("")
continue
if clean_line_lower.startswith("i will "):
continue
if any(clean_line_lower.startswith(prefix) for prefix in skip_prefixes):
continue
if re.match(r'^\s*\w+\([^)]*\)\s*$', clean_line):
continue
cleaned_lines.append(line)
result = "\n".join(cleaned_lines).strip()
result = re.sub(r'\n{3,}', '\n\n', result)
return result
def load_env(env_path: str):
if not os.path.exists(env_path):
return
with open(env_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
def fix_html(text: str) -> str:
"""Fix common HTML issues that cause Telegram to reject parse_mode=HTML."""
# Collapse newlines inside HTML tags (agy may wrap long tags across lines)
text = re.sub(r'<([^>]*?)\n([^>]*?)>', lambda m: '<' + m.group(1).rstrip() + ' ' + m.group(2).lstrip() + '>', text)
# Escape bare & not already part of an HTML entity
text = re.sub(r'&(?!amp;|lt;|gt;|quot;|apos;|#\d+;|#x[0-9a-fA-F]+;)', '&', text)
return text
def send_chunks(token: str, chat_id: str, text: str):
url = f"https://api.telegram.org/bot{token}/sendMessage"
fixed = fix_html(text)
chunks = [fixed[i:i+4000] for i in range(0, len(fixed), 4000)]
for chunk in chunks:
payload = json.dumps({"chat_id": chat_id, "text": chunk, "parse_mode": "HTML"}).encode()
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"})
try:
urllib.request.urlopen(req, timeout=30)
except Exception as e:
print(f"notify_telegram: HTML send failed ({e}), retrying as plain text", file=sys.stderr)
payload = json.dumps({"chat_id": chat_id, "text": chunk}).encode()
req = urllib.request.Request(url, data=payload, headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=30)
def extract_output(payload: dict) -> str:
output = payload.get("output") or payload.get("result") or ""
if not output:
transcript = payload.get("transcript", [])
if isinstance(transcript, list) and transcript:
last = transcript[-1]
if isinstance(last, dict):
output = last.get("content") or last.get("text") or str(last)
else:
output = str(last)
return str(output).strip()
def log_run(task_name: str, status: str, summary: str | None, error: str | None):
"""Write run result to swarm_run_sessions in sessions.db."""
db_path = os.path.abspath(DB_PATH)
if not os.path.exists(db_path):
return
try:
ts = int(datetime.now(timezone.utc).timestamp())
session_id = f"sess_{task_name}_{ts}"
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
conn = sqlite3.connect(db_path)
# get task_id
row = conn.execute(
"SELECT task_id FROM swarm_tasks WHERE task_name = ?", (task_name,)
).fetchone()
task_id = row[0] if row else 0
conn.execute("""
INSERT OR IGNORE INTO swarm_run_sessions
(session_id, task_id, status, started_at, finished_at, digest_summary)
VALUES (?, ?, ?, ?, ?, ?)
""", (session_id, task_id, status, now, now, (summary or error or "")[:500]))
# update last_run_time in swarm_tasks
if status == "SUCCESS":
conn.execute(
"UPDATE swarm_tasks SET last_run_time = ? WHERE task_name = ?",
(now, task_name)
)
conn.commit()
conn.close()
except Exception as e:
print(f"notify_telegram: DB log failed: {e}", file=sys.stderr)
def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
load_env(os.path.join(script_dir, "..", ".env"))
token = os.environ.get("TELEGRAM_BOT_TOKEN", "")
users = [u.strip() for u in os.environ.get("TELEGRAM_ALLOWED_USERS", "").split(",") if u.strip()]
if not token or not users:
log_run(TASK_NAME, "FAILED", None, "Missing TELEGRAM_BOT_TOKEN or TELEGRAM_ALLOWED_USERS")
print(json.dumps({}))
sys.exit(0)
try:
raw = sys.stdin.read()
except Exception:
raw = ""
try:
payload = json.loads(raw) if raw.strip() else {}
output = extract_output(payload)
except json.JSONDecodeError:
# Plain text mode: piped directly from agy stdout (crontab)
output = clean_plain_text(raw)
if len(output) < 50:
log_run(TASK_NAME, "FAILED", None, f"Output too short ({len(output)} chars) — agent may have failed")
print(json.dumps({}))
sys.exit(0)
tg_ok = True
for chat_id in users:
try:
send_chunks(token, chat_id, output)
except Exception as e:
print(f"notify_telegram: failed for {chat_id}: {e}", file=sys.stderr)
tg_ok = False
if tg_ok:
log_run(TASK_NAME, "SUCCESS", output[:500], None)
else:
log_run(TASK_NAME, "FAILED", None, "Telegram delivery failed")
print(json.dumps({}))
if __name__ == "__main__":
main()