feat: RSO observation system, child safety, Discord adapter, Telegram watchdog, email attachments
Core agent improvements: - RSO (Relevance Scoring & Observation) system: interaction_logger, memory_scorer, signal_detector - Memory access logging (memory_access_log table) for relevance scoring; high-signal turn detection - Rich conversation storage for notable turns; compact_conversation truncates long user messages - Task-type classifier (query/action/analysis/creative) for observation tagging - Nested sub-agent visibility: deep delegations now register against the main agent's manager Child safety (Gabriel profile): - child_safety.py: filtering, audit logging, prompt constants for restricted sessions - .kiro/specs/child-safety-profile: requirements, design, tasks specs - GABRIEL_BOT_PROPOSAL.md: initial proposal doc - Reduced context window (10 msgs) and tutor-mode identity for restricted users Telegram adapter: - Polling watchdog: auto-restarts updater if polling drops unexpectedly - get_me() with exponential-backoff retry on NetworkError at startup - Correct stop() ordering: signal watchdog before cancelling tasks Email / Gmail: - send_email: supports file attachments (attachments list param) - get_email: surfaces attachment metadata in response Scheduled tasks / weather: - Remove OpenWeatherMap API calls from morning-weather task; use wttr.in exclusively - New scheduled tasks and scheduler state persistence Discord: - adapters/discord/__init__.py scaffold - discord-plugin: MCP plugin for Claude Code Discord integration (server.ts, skills, config) Infrastructure: - n8n workflow exports (garvis_webhook, content_pipeline variants) - memory_workspace: context, homelab-repo-updates, weekly observation summaries, error logs - UCS C240 migration plan doc - requirements.txt: new deps - .claude/settings.json, fix_hooks.py: hook/permission tuning
This commit is contained in:
120
mcp_tools.py
120
mcp_tools.py
@@ -2568,9 +2568,9 @@ async def get_weather(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
name="send_email",
|
||||
|
||||
description="Send an email via Gmail API. Requires prior OAuth setup (--setup-google).",
|
||||
description="Send an email via Gmail API. Requires prior OAuth setup (--setup-google). Optionally attach local files by providing their absolute paths.",
|
||||
|
||||
input_schema={"to": str, "subject": str, "body": str, "cc": str, "reply_to_message_id": str},
|
||||
input_schema={"to": str, "subject": str, "body": str, "cc": str, "reply_to_message_id": str, "attachments": list},
|
||||
|
||||
)
|
||||
|
||||
@@ -2588,6 +2588,8 @@ async def send_email(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
reply_to_message_id = args.get("reply_to_message_id")
|
||||
|
||||
attachments = args.get("attachments") or []
|
||||
|
||||
|
||||
|
||||
gmail_client, _, _ = _initialize_google_clients()
|
||||
@@ -2612,6 +2614,8 @@ async def send_email(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
reply_to_message_id=reply_to_message_id,
|
||||
|
||||
attachments=attachments or None,
|
||||
|
||||
)
|
||||
|
||||
|
||||
@@ -2620,7 +2624,9 @@ async def send_email(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
msg_id = result.get("message_id", "unknown")
|
||||
|
||||
text = f"Email sent successfully to {to}\nMessage ID: {msg_id}\nSubject: {subject}"
|
||||
attach_note = f"\nAttachments: {len(attachments)} file(s)" if attachments else ""
|
||||
|
||||
text = f"Email sent successfully to {to}\nMessage ID: {msg_id}\nSubject: {subject}{attach_note}"
|
||||
|
||||
else:
|
||||
|
||||
@@ -2740,6 +2746,24 @@ async def get_email(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
email_data = result.get("email", {})
|
||||
|
||||
attachments = email_data.get("attachments", [])
|
||||
|
||||
if attachments:
|
||||
|
||||
attach_lines = "\n".join(
|
||||
|
||||
f" [{i+1}] {a['filename']} ({a['mime_type']}, {a['size']} bytes) — attachment_id: {a['attachment_id']}"
|
||||
|
||||
for i, a in enumerate(attachments)
|
||||
|
||||
)
|
||||
|
||||
attach_section = f"\n\nAttachments ({len(attachments)}):\n{attach_lines}"
|
||||
|
||||
else:
|
||||
|
||||
attach_section = ""
|
||||
|
||||
text = (
|
||||
|
||||
f"From: {email_data.get('from', 'Unknown')}\n"
|
||||
@@ -2748,7 +2772,9 @@ async def get_email(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
f"Subject: {email_data.get('subject', 'No subject')}\n"
|
||||
|
||||
f"Date: {email_data.get('date', 'Unknown')}\n\n"
|
||||
f"Date: {email_data.get('date', 'Unknown')}"
|
||||
|
||||
f"{attach_section}\n\n"
|
||||
|
||||
f"{email_data.get('body', 'No content')}"
|
||||
|
||||
@@ -2769,6 +2795,91 @@ async def get_email(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
|
||||
|
||||
@tool(
|
||||
|
||||
name="download_attachment",
|
||||
|
||||
description=(
|
||||
|
||||
"Download an email attachment to disk. "
|
||||
|
||||
"Use get_email first to retrieve the message_id and attachment_id. "
|
||||
|
||||
"Returns the local file path where the attachment was saved."
|
||||
|
||||
),
|
||||
|
||||
input_schema={"message_id": str, "attachment_id": str, "filename": str, "output_dir": str},
|
||||
|
||||
)
|
||||
|
||||
async def download_attachment(args: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
"""Download an email attachment to disk."""
|
||||
|
||||
message_id = args["message_id"]
|
||||
|
||||
attachment_id = args["attachment_id"]
|
||||
|
||||
filename = args["filename"]
|
||||
|
||||
output_dir = args.get("output_dir", "downloads")
|
||||
|
||||
|
||||
|
||||
gmail_client, _, _ = _initialize_google_clients()
|
||||
|
||||
|
||||
|
||||
if not gmail_client:
|
||||
|
||||
return {
|
||||
|
||||
"content": [{"type": "text", "text": "Error: Google not authorized. Run: python bot_runner.py --setup-google"}],
|
||||
|
||||
"isError": True
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
result = gmail_client.download_attachment(
|
||||
|
||||
message_id=message_id,
|
||||
|
||||
attachment_id=attachment_id,
|
||||
|
||||
filename=filename,
|
||||
|
||||
output_dir=output_dir,
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
if result["success"]:
|
||||
|
||||
text = (
|
||||
|
||||
f"Attachment downloaded: {result['filename']}\n"
|
||||
|
||||
f"Saved to: {result['file_path']}\n"
|
||||
|
||||
f"Size: {result['size']} bytes"
|
||||
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
text = f"Error downloading attachment: {result.get('error', 'Unknown error')}"
|
||||
|
||||
|
||||
|
||||
return {"content": [{"type": "text", "text": text}], "isError": not result["success"]}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@tool(
|
||||
|
||||
@@ -4131,6 +4242,7 @@ file_system_server = create_sdk_mcp_server(
|
||||
send_email,
|
||||
read_emails,
|
||||
get_email,
|
||||
download_attachment,
|
||||
# Calendar tools
|
||||
read_calendar,
|
||||
create_calendar_event,
|
||||
|
||||
Reference in New Issue
Block a user