Merge pull request #80 from AIOSAI/feat/ai_mail-seedgo-compliance-jsonhandler-and-intros
feat(ai_mail): seedgo compliance — json_handler and introspection updates
This commit is contained in:
@@ -36,6 +36,8 @@ from pathlib import Path
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Tuple
|
from typing import Dict, Any, List, Tuple
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# CONSTANTS
|
# CONSTANTS
|
||||||
@@ -290,6 +292,8 @@ def update_central() -> Dict[str, Any]:
|
|||||||
>>> print(stats["system_totals"]["total_unread"])
|
>>> print(stats["system_totals"]["total_unread"])
|
||||||
5
|
5
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("update_central", {"target": str(CENTRAL_FILE)})
|
||||||
|
|
||||||
# Aggregate statistics from all branches
|
# Aggregate statistics from all branches
|
||||||
branch_stats = aggregate_branch_stats()
|
branch_stats = aggregate_branch_stats()
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from urllib.request import Request, urlopen
|
|||||||
from urllib.error import URLError
|
from urllib.error import URLError
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def _find_repo_root() -> Path:
|
def _find_repo_root() -> Path:
|
||||||
@@ -600,6 +601,8 @@ def run_daemon() -> None:
|
|||||||
|
|
||||||
Exits gracefully on SIGTERM/SIGINT or kill switch.
|
Exits gracefully on SIGTERM/SIGINT or kill switch.
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("run_daemon", {"pid": os.getpid()})
|
||||||
|
|
||||||
if not _write_pid_file():
|
if not _write_pid_file():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def _send_bounce(branch_email: str, reason: str, sender: str,
|
def _send_bounce(branch_email: str, reason: str, sender: str,
|
||||||
@@ -98,6 +99,8 @@ def main():
|
|||||||
logger.warning("[monitor] No claude command after --")
|
logger.warning("[monitor] No claude command after --")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
json_handler.log_operation("dispatch_monitor_start", {"branch": branch_email, "sender": sender})
|
||||||
|
|
||||||
# Open stderr log for claude output
|
# Open stderr log for claude output
|
||||||
try:
|
try:
|
||||||
stderr_fh = open(stderr_log, 'a', encoding='utf-8')
|
stderr_fh = open(stderr_log, 'a', encoding='utf-8')
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ from pathlib import Path
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# Dispatch log location (package-relative)
|
# Dispatch log location (package-relative)
|
||||||
_AI_MAIL_DIR = Path(__file__).resolve().parents[3] # ai_mail/
|
_AI_MAIL_DIR = Path(__file__).resolve().parents[3] # ai_mail/
|
||||||
DISPATCH_LOG_FILE = _AI_MAIL_DIR / ".ai_mail.local" / "dispatch_log.json"
|
DISPATCH_LOG_FILE = _AI_MAIL_DIR / ".ai_mail.local" / "dispatch_log.json"
|
||||||
@@ -71,6 +73,8 @@ def log_dispatch(branch: str, pid: Optional[int], status: str, error_msg: Option
|
|||||||
Returns:
|
Returns:
|
||||||
True if logged successfully
|
True if logged successfully
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("log_dispatch", {"branch": branch, "status": status})
|
||||||
|
|
||||||
dispatches = load_dispatch_log()
|
dispatches = load_dispatch_log()
|
||||||
|
|
||||||
entry: Dict[str, Any] = {
|
entry: Dict[str, Any] = {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from pathlib import Path
|
|||||||
from typing import Optional, Tuple, List
|
from typing import Optional, Tuple, List
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def _find_repo_root() -> Path:
|
def _find_repo_root() -> Path:
|
||||||
@@ -316,6 +317,8 @@ def wake_branch(branch_email: str, custom_message: Optional[str] = None,
|
|||||||
Returns:
|
Returns:
|
||||||
Tuple of (DispatchStatus with all steps, overall success bool)
|
Tuple of (DispatchStatus with all steps, overall success bool)
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("wake_branch", {"branch": branch_email, "fresh": fresh, "auto": auto})
|
||||||
|
|
||||||
status = DispatchStatus()
|
status = DispatchStatus()
|
||||||
|
|
||||||
# Step 1: Pause check (auto-dispatch only)
|
# Step 1: Pause check (auto-dispatch only)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from pathlib import Path
|
|||||||
from typing import List, Tuple, Callable, Optional
|
from typing import List, Tuple, Callable, Optional
|
||||||
|
|
||||||
from aipass.prax import logger
|
from aipass.prax import logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def batch_close(
|
def batch_close(
|
||||||
@@ -36,6 +37,7 @@ def batch_close(
|
|||||||
Tuple of (results_list, closed_count, failed_count)
|
Tuple of (results_list, closed_count, failed_count)
|
||||||
results_list contains (message_id, success, message) tuples
|
results_list contains (message_id, success, message) tuples
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("batch_close", {"branch_path": str(branch_path), "count": len(message_ids)})
|
||||||
batch_mode = len(message_ids) > 1
|
batch_mode = len(message_ids) > 1
|
||||||
results = []
|
results = []
|
||||||
closed_count = 0
|
closed_count = 0
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ Independent handler - no module dependencies.
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# Lazy imports
|
# Lazy imports
|
||||||
_append_footer = None
|
_append_footer = None
|
||||||
@@ -52,6 +53,7 @@ def create_email_file(to_branch: str, subject: str, message: str, user_info: Dic
|
|||||||
Returns:
|
Returns:
|
||||||
Path to created email file in sent folder
|
Path to created email file in sent folder
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("create_email_file", {"to": to_branch, "subject": subject})
|
||||||
timestamp = datetime.now()
|
timestamp = datetime.now()
|
||||||
timestamp_str = timestamp.strftime(user_info.get("timestamp_format", "%Y-%m-%d %H:%M:%S"))
|
timestamp_str = timestamp.strftime(user_info.get("timestamp_format", "%Y-%m-%d %H:%M:%S"))
|
||||||
|
|
||||||
@@ -110,7 +112,7 @@ def _trigger_sent_purge(mailbox_path: Path) -> None:
|
|||||||
logger.warning("[create] _trigger_sent_purge() failed: %s", e)
|
logger.warning("[create] _trigger_sent_purge() failed: %s", e)
|
||||||
|
|
||||||
|
|
||||||
def load_email_file(email_file: Path) -> Dict | None:
|
def load_email_file(email_file: Path) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Load email data from file.
|
Load email data from file.
|
||||||
|
|
||||||
@@ -158,7 +160,7 @@ if __name__ == "__main__":
|
|||||||
c.print()
|
c.print()
|
||||||
c.print("FUNCTIONS PROVIDED:")
|
c.print("FUNCTIONS PROVIDED:")
|
||||||
c.print(" - create_email_file(to_branch, subject, message, user_info) -> Path")
|
c.print(" - create_email_file(to_branch, subject, message, user_info) -> Path")
|
||||||
c.print(" - load_email_file(email_file) -> Dict | None")
|
c.print(" - load_email_file(email_file) -> Optional[Dict]")
|
||||||
c.print(" - sanitize_subject(subject, max_length) -> str")
|
c.print(" - sanitize_subject(subject, max_length) -> str")
|
||||||
c.print()
|
c.print()
|
||||||
c.print("HANDLER CHARACTERISTICS:")
|
c.print("HANDLER CHARACTERISTICS:")
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from datetime import datetime
|
|||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# Lazy-loaded write_section reference
|
# Lazy-loaded write_section reference
|
||||||
_write_section = None
|
_write_section = None
|
||||||
@@ -155,6 +156,7 @@ def push_dashboard_update(branch_path: Path) -> bool:
|
|||||||
Returns:
|
Returns:
|
||||||
True if update succeeded, False on any error (never raises)
|
True if update succeeded, False on any error (never raises)
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("dashboard_sync", {"branch_path": str(branch_path)})
|
||||||
try:
|
try:
|
||||||
branch_path = Path(branch_path)
|
branch_path = Path(branch_path)
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from typing import Dict, Tuple, List, Optional, Callable
|
|||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
from aipass.ai_mail.apps.handlers.json_utils.json_handler import load_json, save_json
|
from aipass.ai_mail.apps.handlers.json_utils.json_handler import load_json, save_json
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def _find_repo_root() -> Path:
|
def _find_repo_root() -> Path:
|
||||||
@@ -242,6 +243,8 @@ def deliver_email_to_branch(
|
|||||||
Tuple of (success: bool, error_message: str)
|
Tuple of (success: bool, error_message: str)
|
||||||
error_message is empty string if successful
|
error_message is empty string if successful
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("deliver_email", {"to": to_branch, "subject": email_data.get("subject", "")})
|
||||||
|
|
||||||
# Handle path input from DRONE's @ resolution
|
# Handle path input from DRONE's @ resolution
|
||||||
if to_branch.startswith('/'):
|
if to_branch.startswith('/'):
|
||||||
branches_list = get_all_branches()
|
branches_list = get_all_branches()
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from datetime import datetime
|
|||||||
from typing import Dict, Any, Callable, Optional
|
from typing import Dict, Any, Callable, Optional
|
||||||
|
|
||||||
from aipass.prax import logger
|
from aipass.prax import logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def build_error_report(to_branch: str, subject: str, error_msg: str) -> Dict[str, Any]:
|
def build_error_report(to_branch: str, subject: str, error_msg: str) -> Dict[str, Any]:
|
||||||
@@ -75,6 +76,8 @@ def dispatch_send_error(
|
|||||||
Returns:
|
Returns:
|
||||||
True if error dispatched successfully, False otherwise.
|
True if error dispatched successfully, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("dispatch_send_error", {"to_branch": to_branch, "subject": subject})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
email_data = build_error_report(to_branch, subject, error_msg)
|
email_data = build_error_report(to_branch, subject, error_msg)
|
||||||
deliver_fn("@drone", email_data)
|
deliver_fn("@drone", email_data)
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ Reminds branches of process steps: Seed audit, memory update, FPLAN close, confi
|
|||||||
Independent handler - no module dependencies.
|
Independent handler - no module dependencies.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# Standard footer for all outgoing emails
|
# Standard footer for all outgoing emails
|
||||||
STANDARD_FOOTER = """
|
STANDARD_FOOTER = """
|
||||||
---
|
---
|
||||||
@@ -47,6 +49,7 @@ def append_footer(message: str) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
Message with footer appended
|
Message with footer appended
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("append_footer", {"message_length": len(message)})
|
||||||
return message + get_footer()
|
return message + get_footer()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ from pathlib import Path
|
|||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def _find_repo_root() -> Path:
|
def _find_repo_root() -> Path:
|
||||||
"""Walk up from this file to find AIPASS_REGISTRY.json (repo root)."""
|
"""Walk up from this file to find AIPASS_REGISTRY.json (repo root)."""
|
||||||
@@ -104,6 +106,7 @@ def format_email_header(email_data: Dict) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
Formatted header string
|
Formatted header string
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("format_email_header", {"subject": email_data.get("subject", "")})
|
||||||
sender = format_sender_display(
|
sender = format_sender_display(
|
||||||
email_data.get('from_name', 'Unknown'),
|
email_data.get('from_name', 'Unknown'),
|
||||||
email_data.get('from', 'unknown')
|
email_data.get('from', 'unknown')
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Email Header Handler
|
Email Header Handler
|
||||||
|
|
||||||
@@ -68,6 +70,7 @@ def prepend_dispatch_header(message: str, no_memory_save: bool = False) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
Message with dispatch header prepended
|
Message with dispatch header prepended
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("prepend_dispatch_header", {"no_memory_save": no_memory_save})
|
||||||
return get_dispatch_header(no_memory_save=no_memory_save) + message
|
return get_dispatch_header(no_memory_save=no_memory_save) + message
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from datetime import datetime
|
|||||||
from typing import Dict, Tuple, Optional, Any
|
from typing import Dict, Tuple, Optional, Any
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
# Lazy import for inbox file lock
|
# Lazy import for inbox file lock
|
||||||
@@ -45,7 +46,7 @@ def _get_console() -> Any:
|
|||||||
def _get_update_section() -> Any:
|
def _get_update_section() -> Any:
|
||||||
"""Lazy import update_section."""
|
"""Lazy import update_section."""
|
||||||
try:
|
try:
|
||||||
from aipass.devpulse.apps.modules.dashboard import update_section
|
from aipass.devpulse.apps.modules.dashboard import update_section # type: ignore[import-not-found]
|
||||||
return update_section
|
return update_section
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return None
|
return None
|
||||||
@@ -154,6 +155,7 @@ def mark_read_and_archive(branch_path: Path, message_id: str) -> Tuple[bool, str
|
|||||||
Returns:
|
Returns:
|
||||||
Tuple of (success: bool, message: str)
|
Tuple of (success: bool, message: str)
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("inbox_cleanup", {"branch_path": str(branch_path), "message_id": message_id})
|
||||||
mailbox_path = branch_path / ".ai_mail.local"
|
mailbox_path = branch_path / ".ai_mail.local"
|
||||||
inbox_file = mailbox_path / "inbox.json"
|
inbox_file = mailbox_path / "inbox.json"
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# fcntl is POSIX-only (Linux/macOS). On Windows, use msvcrt for locking.
|
# fcntl is POSIX-only (Linux/macOS). On Windows, use msvcrt for locking.
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
import msvcrt
|
import msvcrt
|
||||||
@@ -49,6 +51,7 @@ def inbox_lock(inbox_file: Path):
|
|||||||
Raises:
|
Raises:
|
||||||
OSError: If lock cannot be acquired
|
OSError: If lock cannot be acquired
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("inbox_lock", {"inbox_file": str(inbox_file)})
|
||||||
lock_file = inbox_file.parent / ".inbox.lock"
|
lock_file = inbox_file.parent / ".inbox.lock"
|
||||||
lock_fd = None
|
lock_fd = None
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from pathlib import Path
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ def load_inbox(inbox_file: Path) -> Dict:
|
|||||||
Raises:
|
Raises:
|
||||||
Exception: If file cannot be read or parsed
|
Exception: If file cannot be read or parsed
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("load_inbox", {"inbox_file": str(inbox_file)})
|
||||||
if not inbox_file.exists():
|
if not inbox_file.exists():
|
||||||
return {"messages": []}
|
return {"messages": []}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ Independent handler - no module or display dependencies.
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Optional, Any, Callable, Tuple
|
from typing import Dict, Optional, Any, Callable, Tuple
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def resolve_inbox_target(
|
def resolve_inbox_target(
|
||||||
args_first: Optional[str],
|
args_first: Optional[str],
|
||||||
@@ -40,6 +42,7 @@ def resolve_inbox_target(
|
|||||||
target_branch: str | None (the explicit target, or None for current)
|
target_branch: str | None (the explicit target, or None for current)
|
||||||
error: str | None (set when success is False)
|
error: str | None (set when success is False)
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("resolve_inbox_target", {"target": args_first})
|
||||||
target_branch = None
|
target_branch = None
|
||||||
if args_first and args_first.startswith("@"):
|
if args_first and args_first.startswith("@"):
|
||||||
target_branch = args_first
|
target_branch = args_first
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from datetime import datetime
|
|||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# Purge configuration
|
# Purge configuration
|
||||||
MAX_EMAILS = 10
|
MAX_EMAILS = 10
|
||||||
@@ -275,6 +276,7 @@ def run_purge(mailbox_path: Path) -> Dict[str, Any]:
|
|||||||
Returns:
|
Returns:
|
||||||
Dict with combined results
|
Dict with combined results
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("run_purge", {"mailbox_path": str(mailbox_path)})
|
||||||
sent_result = purge_sent_folder(mailbox_path)
|
sent_result = purge_sent_folder(mailbox_path)
|
||||||
deleted_result = purge_deleted_folder(mailbox_path)
|
deleted_result = purge_deleted_folder(mailbox_path)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from typing import Dict, Tuple, Optional
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# Services imported in __main__ only (handlers should not display)
|
# Services imported in __main__ only (handlers should not display)
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ def send_reply(
|
|||||||
Returns:
|
Returns:
|
||||||
Tuple of (success: bool, message: str, reply_id: str or None)
|
Tuple of (success: bool, message: str, reply_id: str or None)
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("send_reply", {"from_branch": str(from_branch_path), "reply_to": original_email.get("from", "unknown")})
|
||||||
# Import here to avoid circular imports
|
# Import here to avoid circular imports
|
||||||
from aipass.ai_mail.apps.handlers.email.delivery import deliver_email_to_branch, get_all_branches
|
from aipass.ai_mail.apps.handlers.email.delivery import deliver_email_to_branch, get_all_branches
|
||||||
from aipass.ai_mail.apps.handlers.email.inbox_cleanup import mark_as_closed_and_archive
|
from aipass.ai_mail.apps.handlers.email.inbox_cleanup import mark_as_closed_and_archive
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from pathlib import Path
|
|||||||
from typing import Optional, Tuple, List, Dict, Any
|
from typing import Optional, Tuple, List, Dict, Any
|
||||||
|
|
||||||
from aipass.prax import logger
|
from aipass.prax import logger
|
||||||
# logger imported from aipass.prax
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def resolve_sender_info(
|
def resolve_sender_info(
|
||||||
@@ -41,6 +41,7 @@ def resolve_sender_info(
|
|||||||
Returns:
|
Returns:
|
||||||
Dict with email_address, display_name, mailbox_path, timestamp_format.
|
Dict with email_address, display_name, mailbox_path, timestamp_format.
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("resolve_sender", {"from_branch": from_branch})
|
||||||
if from_branch:
|
if from_branch:
|
||||||
email_addr = f"@{from_branch.lstrip('@').lower()}"
|
email_addr = f"@{from_branch.lstrip('@').lower()}"
|
||||||
branch_info = get_branch_by_email_fn(email_addr)
|
branch_info = get_branch_by_email_fn(email_addr)
|
||||||
@@ -81,13 +82,14 @@ def send_to_broadcast(
|
|||||||
on_delivered_callback,
|
on_delivered_callback,
|
||||||
log_operation_fn,
|
log_operation_fn,
|
||||||
update_central_fn,
|
update_central_fn,
|
||||||
) -> Tuple[bool, int, int, Optional[str]]:
|
) -> Tuple[bool, int, int, Any]:
|
||||||
"""
|
"""
|
||||||
Execute broadcast send to all branches.
|
Execute broadcast send to all branches.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (success, success_count, total_count, error_msg).
|
Tuple of (success, success_count, total_count, results_or_error).
|
||||||
error_msg is set if the email file could not be loaded.
|
On failure: 4th element is an error string.
|
||||||
|
On success: 4th element is a list of (branch_name, success, error_msg) tuples.
|
||||||
"""
|
"""
|
||||||
email_file = create_email_file_fn("all", subject, message, user_info, reply_to=reply_to, dispatched_to=dispatched_to)
|
email_file = create_email_file_fn("all", subject, message, user_info, reply_to=reply_to, dispatched_to=dispatched_to)
|
||||||
email_data = load_email_file_fn(email_file)
|
email_data = load_email_file_fn(email_file)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ Independent handler - no module or display dependencies.
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict, Optional, Any
|
from typing import List, Dict, Optional, Any
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def parse_send_args(args: List[str]) -> Dict[str, Any]:
|
def parse_send_args(args: List[str]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@@ -35,6 +37,7 @@ def parse_send_args(args: List[str]) -> Dict[str, Any]:
|
|||||||
mode: 'direct' | 'interactive' | 'error'
|
mode: 'direct' | 'interactive' | 'error'
|
||||||
error: str | None (set when mode=='error')
|
error: str | None (set when mode=='error')
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("parse_send_args", {"arg_count": len(args)})
|
||||||
working_args = list(args)
|
working_args = list(args)
|
||||||
|
|
||||||
# Extract --dispatch / --auto-execute
|
# Extract --dispatch / --auto-execute
|
||||||
|
|||||||
@@ -24,4 +24,5 @@ from aipass.ai_mail.apps.handlers.json_utils.json_handler import ( # noqa: F401
|
|||||||
save_json,
|
save_json,
|
||||||
ensure_json_exists,
|
ensure_json_exists,
|
||||||
get_json_path,
|
get_json_path,
|
||||||
|
log_operation,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import re
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Dict, Tuple
|
from typing import Optional, Dict, Tuple
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def _find_repo_root() -> Path:
|
def _find_repo_root() -> Path:
|
||||||
"""Walk up from this file to find AIPASS_REGISTRY.json (repo root)."""
|
"""Walk up from this file to find AIPASS_REGISTRY.json (repo root)."""
|
||||||
@@ -63,6 +65,8 @@ def parse_error_log_line(log_line: str) -> Optional[Dict]:
|
|||||||
Dict with timestamp, logger_name, module_name, level, message
|
Dict with timestamp, logger_name, module_name, level, message
|
||||||
None if not an ERROR line or parsing fails
|
None if not an ERROR line or parsing fails
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("parse_error_log_line", {"log_line_length": len(log_line)})
|
||||||
|
|
||||||
match = re.match(LOG_PATTERN, log_line.strip())
|
match = re.match(LOG_PATTERN, log_line.strip())
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Architecture:
|
|||||||
# =============================================
|
# =============================================
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
# =============================================
|
# =============================================
|
||||||
# CONSTANTS
|
# CONSTANTS
|
||||||
@@ -107,6 +109,8 @@ def get_health_info(file_path: Path | str) -> dict:
|
|||||||
Returns:
|
Returns:
|
||||||
Dict with line_count, status, needs_email
|
Dict with line_count, status, needs_email
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("get_health_info", {"file_path": str(file_path)})
|
||||||
|
|
||||||
line_count = count_file_lines(file_path)
|
line_count = count_file_lines(file_path)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def send_notification(title: str, body: str, source: str = "ai_mail",
|
def send_notification(title: str, body: str, source: str = "ai_mail",
|
||||||
@@ -36,6 +37,8 @@ def send_notification(title: str, body: str, source: str = "ai_mail",
|
|||||||
Returns:
|
Returns:
|
||||||
True if sent, False on failure
|
True if sent, False on failure
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("send_notification", {"title": title, "source": source})
|
||||||
|
|
||||||
# Primary: dbus direct (bypasses Portal, supports stacking)
|
# Primary: dbus direct (bypasses Portal, supports stacking)
|
||||||
if _send_via_dbus(title, body, source, icon):
|
if _send_via_dbus(title, body, source, icon):
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import json
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
def load_registry(registry_file: Path) -> Dict:
|
def load_registry(registry_file: Path) -> Dict:
|
||||||
"""
|
"""
|
||||||
@@ -35,6 +37,8 @@ def load_registry(registry_file: Path) -> Dict:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("load_registry", {"registry_file": str(registry_file)})
|
||||||
|
|
||||||
if not registry_file.exists():
|
if not registry_file.exists():
|
||||||
return {
|
return {
|
||||||
"last_updated": "",
|
"last_updated": "",
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ Handler Independence:
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict
|
from typing import List, Dict, Optional
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
@@ -53,6 +55,8 @@ def get_all_branches() -> List[Dict]:
|
|||||||
Note:
|
Note:
|
||||||
Returns empty list if registry not found or on error.
|
Returns empty list if registry not found or on error.
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("get_all_branches", {"registry_path": str(BRANCH_REGISTRY_PATH)})
|
||||||
|
|
||||||
branches = []
|
branches = []
|
||||||
|
|
||||||
if not BRANCH_REGISTRY_PATH.exists():
|
if not BRANCH_REGISTRY_PATH.exists():
|
||||||
@@ -118,7 +122,7 @@ def _derive_email_from_branch_name(branch_name: str) -> str:
|
|||||||
return f"@{email_part}"
|
return f"@{email_part}"
|
||||||
|
|
||||||
|
|
||||||
def get_branch_by_email(email: str) -> Dict | None:
|
def get_branch_by_email(email: str) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Get branch information by email address.
|
Get branch information by email address.
|
||||||
|
|
||||||
@@ -170,7 +174,7 @@ if __name__ == "__main__":
|
|||||||
console.print()
|
console.print()
|
||||||
console.print("FUNCTIONS PROVIDED:")
|
console.print("FUNCTIONS PROVIDED:")
|
||||||
console.print(" - get_all_branches() -> List[Dict]")
|
console.print(" - get_all_branches() -> List[Dict]")
|
||||||
console.print(" - get_branch_by_email(email) -> Dict | None")
|
console.print(" - get_branch_by_email(email) -> Optional[Dict]")
|
||||||
console.print(" - get_branch_email_map() -> Dict[str, str]")
|
console.print(" - get_branch_email_map() -> Dict[str, str]")
|
||||||
console.print(" - get_branch_path_map() -> Dict[str, str]")
|
console.print(" - get_branch_path_map() -> Dict[str, str]")
|
||||||
console.print()
|
console.print()
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ from pathlib import Path
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
MODULE_NAME = "registry.update"
|
MODULE_NAME = "registry.update"
|
||||||
@@ -56,6 +58,8 @@ def ping_registry(
|
|||||||
Returns:
|
Returns:
|
||||||
True if registry updated successfully, False otherwise
|
True if registry updated successfully, False otherwise
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("ping_registry", {"branch_name": branch_name, "branch_path": str(branch_path)})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Ensure registry directory exists
|
# Ensure registry directory exists
|
||||||
REGISTRY_PATH.parent.mkdir(parents=True, exist_ok=True)
|
REGISTRY_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ Walks up directory tree to find branch root (has .trinity/passport.json).
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# =============================================
|
# =============================================
|
||||||
# CONSTANTS
|
# CONSTANTS
|
||||||
@@ -55,7 +56,7 @@ def _get_branches_list(registry: dict) -> list:
|
|||||||
# BRANCH DETECTION FUNCTIONS
|
# BRANCH DETECTION FUNCTIONS
|
||||||
# =============================================
|
# =============================================
|
||||||
|
|
||||||
def detect_branch_from_pwd() -> Dict | None:
|
def detect_branch_from_pwd() -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Detect which branch is calling based on current working directory.
|
Detect which branch is calling based on current working directory.
|
||||||
|
|
||||||
@@ -73,6 +74,8 @@ def detect_branch_from_pwd() -> Dict | None:
|
|||||||
}
|
}
|
||||||
None if no branch detected
|
None if no branch detected
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("detect_branch_from_pwd", {"cwd": str(Path.cwd())})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Primary: use explicit branch name passed by drone (works in Docker + local)
|
# Primary: use explicit branch name passed by drone (works in Docker + local)
|
||||||
caller_branch = os.environ.get("AIPASS_CALLER_BRANCH")
|
caller_branch = os.environ.get("AIPASS_CALLER_BRANCH")
|
||||||
@@ -102,7 +105,7 @@ def detect_branch_from_pwd() -> Dict | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _lookup_branch_by_name(branch_name: str) -> Dict | None:
|
def _lookup_branch_by_name(branch_name: str) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Look up branch in the registry by name (case-insensitive).
|
Look up branch in the registry by name (case-insensitive).
|
||||||
|
|
||||||
@@ -135,7 +138,7 @@ def _lookup_branch_by_name(branch_name: str) -> Dict | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_branch_root(start_path: Path) -> Path | None:
|
def find_branch_root(start_path: Path) -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
Walk up directory tree to find branch root.
|
Walk up directory tree to find branch root.
|
||||||
|
|
||||||
@@ -165,7 +168,7 @@ def find_branch_root(start_path: Path) -> Path | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_branch_info_from_registry(branch_path: Path) -> Dict | None:
|
def get_branch_info_from_registry(branch_path: Path) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Look up branch information in BRANCH_REGISTRY.json by path.
|
Look up branch information in BRANCH_REGISTRY.json by path.
|
||||||
|
|
||||||
@@ -250,9 +253,9 @@ if __name__ == "__main__":
|
|||||||
console.print(" Walks up directory tree to find branch root")
|
console.print(" Walks up directory tree to find branch root")
|
||||||
console.print()
|
console.print()
|
||||||
console.print("FUNCTIONS PROVIDED:")
|
console.print("FUNCTIONS PROVIDED:")
|
||||||
console.print(" - detect_branch_from_pwd() -> Dict | None")
|
console.print(" - detect_branch_from_pwd() -> Optional[Dict]")
|
||||||
console.print(" - find_branch_root(start_path) -> Path | None")
|
console.print(" - find_branch_root(start_path) -> Optional[Path]")
|
||||||
console.print(" - get_branch_info_from_registry(branch_path) -> Dict | None")
|
console.print(" - get_branch_info_from_registry(branch_path) -> Optional[Dict]")
|
||||||
console.print(" - get_branch_display_name(branch_info) -> str")
|
console.print(" - get_branch_display_name(branch_info) -> str")
|
||||||
console.print(" - get_local_config_path(branch_path) -> Path")
|
console.print(" - get_local_config_path(branch_path) -> Path")
|
||||||
console.print()
|
console.print()
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ PHILOSOPHY: Fail hard if detection fails. Fallbacks hide bugs.
|
|||||||
# IMPORTS
|
# IMPORTS
|
||||||
# =============================================
|
# =============================================
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
# Import branch detection functions
|
# Import branch detection functions
|
||||||
from .branch_detection import detect_branch_from_pwd
|
from .branch_detection import detect_branch_from_pwd
|
||||||
@@ -49,6 +50,8 @@ def get_current_user() -> Dict:
|
|||||||
Raises:
|
Raises:
|
||||||
RuntimeError: If branch detection fails (not called from a branch directory)
|
RuntimeError: If branch detection fails (not called from a branch directory)
|
||||||
"""
|
"""
|
||||||
|
json_handler.log_operation("get_current_user", {"cwd": str(Path.cwd())})
|
||||||
|
|
||||||
# Detect branch from PWD
|
# Detect branch from PWD
|
||||||
branch_info = detect_branch_from_pwd()
|
branch_info = detect_branch_from_pwd()
|
||||||
|
|
||||||
@@ -93,7 +96,7 @@ def get_current_user() -> Dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_user_by_email(email: str) -> Dict | None:
|
def get_user_by_email(email: str) -> Optional[Dict]:
|
||||||
"""
|
"""
|
||||||
Get user information by email address from BRANCH_REGISTRY.json
|
Get user information by email address from BRANCH_REGISTRY.json
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from aipass.ai_mail.apps.handlers.registry.update import (
|
|||||||
get_branch_context,
|
get_branch_context,
|
||||||
update_json_memory_health
|
update_json_memory_health
|
||||||
)
|
)
|
||||||
from aipass.ai_mail.apps.handlers.json_utils.json_handler import log_operation
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
|
|
||||||
MODULE_NAME = "branch_ping"
|
MODULE_NAME = "branch_ping"
|
||||||
THRESHOLDS = {"green": (0, 400), "yellow": (401, 550), "red": (551, float('inf'))}
|
THRESHOLDS = {"green": (0, 400), "yellow": (401, 550), "red": (551, float('inf'))}
|
||||||
@@ -55,7 +55,7 @@ def handle_ping(verbose: bool = False) -> bool:
|
|||||||
obs_status = {"line_count": obs_count, "status": obs_status_code}
|
obs_status = {"line_count": obs_count, "status": obs_status_code}
|
||||||
ping_registry(branch_name, cwd, local_status, obs_status)
|
ping_registry(branch_name, cwd, local_status, obs_status)
|
||||||
|
|
||||||
log_operation("ping_executed", {"branch": branch_name, "local_count": local_count, "obs_count": obs_count})
|
json_handler.log_operation("ping_executed", {"branch": branch_name, "local_count": local_count, "obs_count": obs_count})
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
console.print(f"Ping successful for {branch_name}")
|
console.print(f"Ping successful for {branch_name}")
|
||||||
@@ -172,6 +172,10 @@ def handle_command(command: str, args: List[str]) -> bool:
|
|||||||
print_help()
|
print_help()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
print_introspection()
|
||||||
|
return True
|
||||||
|
|
||||||
if command == "ping":
|
if command == "ping":
|
||||||
return handle_ping("--verbose" in args or "-v" in args)
|
return handle_ping("--verbose" in args or "-v" in args)
|
||||||
elif command == "status":
|
elif command == "status":
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from typing import List
|
|||||||
|
|
||||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||||
from aipass.cli.apps.modules import console, error
|
from aipass.cli.apps.modules import console, error
|
||||||
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
from aipass.ai_mail.apps.handlers.dispatch.status import (
|
from aipass.ai_mail.apps.handlers.dispatch.status import (
|
||||||
load_dispatch_log,
|
load_dispatch_log,
|
||||||
check_pid_status,
|
check_pid_status,
|
||||||
@@ -81,11 +82,13 @@ def handle_command(command: str, args: List[str]) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
print_help()
|
print_introspection()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
subcommand = args[0]
|
subcommand = args[0]
|
||||||
|
|
||||||
|
json_handler.log_operation("dispatch_command", {"subcommand": subcommand})
|
||||||
|
|
||||||
if subcommand == "status":
|
if subcommand == "status":
|
||||||
return _orchestrate_status()
|
return _orchestrate_status()
|
||||||
elif subcommand == "daemon":
|
elif subcommand == "daemon":
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ from aipass.ai_mail.apps.handlers.email.reply import get_email_by_id, send_reply
|
|||||||
from aipass.ai_mail.apps.handlers.email.header import prepend_dispatch_header
|
from aipass.ai_mail.apps.handlers.email.header import prepend_dispatch_header
|
||||||
from aipass.ai_mail.apps.handlers.users.user import get_current_user
|
from aipass.ai_mail.apps.handlers.users.user import get_current_user
|
||||||
from aipass.ai_mail.apps.handlers.registry.read import get_all_branches, get_branch_by_email
|
from aipass.ai_mail.apps.handlers.registry.read import get_all_branches, get_branch_by_email
|
||||||
from aipass.ai_mail.apps.handlers.json_utils.json_handler import log_operation
|
from aipass.ai_mail.apps.handlers.json import json_handler
|
||||||
from aipass.ai_mail.apps.handlers.email.send import (
|
from aipass.ai_mail.apps.handlers.email.send import (
|
||||||
resolve_sender_info, send_to_broadcast, send_to_single, collect_interactive_input
|
resolve_sender_info, send_to_broadcast, send_to_single, collect_interactive_input
|
||||||
)
|
)
|
||||||
@@ -116,7 +116,7 @@ def handle_command(command: str, args: List[str]) -> bool:
|
|||||||
|
|
||||||
def handle_send(args: List[str]) -> bool:
|
def handle_send(args: List[str]) -> bool:
|
||||||
"""Orchestrate email sending workflow."""
|
"""Orchestrate email sending workflow."""
|
||||||
log_operation("send_email_initiated", {"args_count": len(args)})
|
json_handler.log_operation("send_email_initiated", {"args_count": len(args)})
|
||||||
parsed = parse_send_args(args)
|
parsed = parse_send_args(args)
|
||||||
|
|
||||||
if parsed["mode"] == "error":
|
if parsed["mode"] == "error":
|
||||||
@@ -192,7 +192,7 @@ def _send_direct(to_branch, subject, message, auto_execute=False,
|
|||||||
success, error_msg = send_to_single(
|
success, error_msg = send_to_single(
|
||||||
to_branch, subject, message, user_info, auto_execute, no_memory_save,
|
to_branch, subject, message, user_info, auto_execute, no_memory_save,
|
||||||
reply_to, dispatched_to, create_email_file, load_email_file,
|
reply_to, dispatched_to, create_email_file, load_email_file,
|
||||||
deliver_email_to_branch, _delivery_callback, log_operation, update_central)
|
deliver_email_to_branch, _delivery_callback, json_handler.log_operation, update_central)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
label = f"\\[dispatch: queued for daemon]" if auto_execute else ""
|
label = f"\\[dispatch: queued for daemon]" if auto_execute else ""
|
||||||
@@ -205,7 +205,7 @@ def _send_direct(to_branch, subject, message, auto_execute=False,
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
error(f"Failed to deliver: {error_msg}")
|
error(f"Failed to deliver: {error_msg}")
|
||||||
dispatch_send_error(to_branch, subject, error_msg, deliver_email_to_branch)
|
dispatch_send_error(to_branch, subject, error_msg or "", deliver_email_to_branch)
|
||||||
return False
|
return False
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
logger.info("[email] Send: broken pipe (stdout closed early)")
|
logger.info("[email] Send: broken pipe (stdout closed early)")
|
||||||
@@ -224,11 +224,11 @@ def _send_broadcast(subject, message, user_info, auto_execute, no_memory_save, r
|
|||||||
ok, success_count, total, results = send_to_broadcast(
|
ok, success_count, total, results = send_to_broadcast(
|
||||||
subject, message, user_info, auto_execute, no_memory_save, reply_to, dispatched_to,
|
subject, message, user_info, auto_execute, no_memory_save, reply_to, dispatched_to,
|
||||||
branches, create_email_file, load_email_file, deliver_email_to_branch,
|
branches, create_email_file, load_email_file, deliver_email_to_branch,
|
||||||
_delivery_callback, log_operation, update_central)
|
_delivery_callback, json_handler.log_operation, update_central)
|
||||||
if isinstance(results, str):
|
if isinstance(results, str) or results is None:
|
||||||
error("Failed to load email file for broadcast")
|
error("Failed to load email file for broadcast")
|
||||||
return False
|
return False
|
||||||
for name, ok, err in results:
|
for name, ok, err in results: # type: ignore[union-attr]
|
||||||
if ok:
|
if ok:
|
||||||
console.print(f" [green]OK[/green] {name}")
|
console.print(f" [green]OK[/green] {name}")
|
||||||
else:
|
else:
|
||||||
@@ -239,7 +239,7 @@ def _send_broadcast(subject, message, user_info, auto_execute, no_memory_save, r
|
|||||||
|
|
||||||
def handle_inbox(args: List[str]) -> bool:
|
def handle_inbox(args: List[str]) -> bool:
|
||||||
"""Orchestrate inbox viewing."""
|
"""Orchestrate inbox viewing."""
|
||||||
log_operation("inbox_viewed")
|
json_handler.log_operation("inbox_viewed")
|
||||||
try:
|
try:
|
||||||
first_arg = args[0] if args else None
|
first_arg = args[0] if args else None
|
||||||
ok, info = resolve_inbox_target(first_arg, _REPO_ROOT, get_branch_by_email, get_current_user)
|
ok, info = resolve_inbox_target(first_arg, _REPO_ROOT, get_branch_by_email, get_current_user)
|
||||||
@@ -278,14 +278,14 @@ def handle_inbox(args: List[str]) -> bool:
|
|||||||
|
|
||||||
def handle_view(args: List[str]) -> bool:
|
def handle_view(args: List[str]) -> bool:
|
||||||
"""View email content and mark as opened."""
|
"""View email content and mark as opened."""
|
||||||
log_operation("view_email_initiated", {"args": args})
|
json_handler.log_operation("view_email_initiated", {"args": args})
|
||||||
if not args:
|
if not args:
|
||||||
error("Usage: drone @ai_mail view <message_id>")
|
error("Usage: drone @ai_mail view <message_id>")
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
branch_path = Path(get_current_user()["mailbox_path"]).parent
|
branch_path = Path(get_current_user()["mailbox_path"]).parent
|
||||||
success, message, email_data = mark_as_opened(branch_path, args[0])
|
success, message, email_data = mark_as_opened(branch_path, args[0])
|
||||||
if not success:
|
if not success or email_data is None:
|
||||||
error(message)
|
error(message)
|
||||||
return False
|
return False
|
||||||
console.print(f"\n{'='*60}")
|
console.print(f"\n{'='*60}")
|
||||||
@@ -296,7 +296,7 @@ def handle_view(args: List[str]) -> bool:
|
|||||||
console.print(f"[dim]Status: opened | ID: {args[0]}[/dim]")
|
console.print(f"[dim]Status: opened | ID: {args[0]}[/dim]")
|
||||||
console.print(f"[dim]To reply: drone @ai_mail reply {args[0]} \"your message\"[/dim]")
|
console.print(f"[dim]To reply: drone @ai_mail reply {args[0]} \"your message\"[/dim]")
|
||||||
console.print(f"[dim]To close: drone @ai_mail close {args[0]}[/dim]")
|
console.print(f"[dim]To close: drone @ai_mail close {args[0]}[/dim]")
|
||||||
log_operation("email_viewed", {"message_id": args[0]})
|
json_handler.log_operation("email_viewed", {"message_id": args[0]})
|
||||||
return True
|
return True
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
return True
|
return True
|
||||||
@@ -308,7 +308,7 @@ def handle_view(args: List[str]) -> bool:
|
|||||||
|
|
||||||
def handle_close(args: List[str]) -> bool:
|
def handle_close(args: List[str]) -> bool:
|
||||||
"""Close email(s) and archive to deleted."""
|
"""Close email(s) and archive to deleted."""
|
||||||
log_operation("close_email_initiated", {"args": args})
|
json_handler.log_operation("close_email_initiated", {"args": args})
|
||||||
if not args:
|
if not args:
|
||||||
error("Usage: drone @ai_mail close <id> [id2 ...] | close all")
|
error("Usage: drone @ai_mail close <id> [id2 ...] | close all")
|
||||||
return False
|
return False
|
||||||
@@ -321,7 +321,7 @@ def handle_close(args: List[str]) -> bool:
|
|||||||
else:
|
else:
|
||||||
error(message)
|
error(message)
|
||||||
if success:
|
if success:
|
||||||
log_operation("email_closed_all", {"count": count})
|
json_handler.log_operation("email_closed_all", {"count": count})
|
||||||
return success
|
return success
|
||||||
|
|
||||||
results, closed, failed = batch_close(branch_path, args, mark_as_closed_and_archive)
|
results, closed, failed = batch_close(branch_path, args, mark_as_closed_and_archive)
|
||||||
@@ -331,7 +331,7 @@ def handle_close(args: List[str]) -> bool:
|
|||||||
else:
|
else:
|
||||||
error(message)
|
error(message)
|
||||||
if success:
|
if success:
|
||||||
log_operation("email_closed", {"message_id": msg_id})
|
json_handler.log_operation("email_closed", {"message_id": msg_id})
|
||||||
|
|
||||||
if len(args) > 1 and closed > 0:
|
if len(args) > 1 and closed > 0:
|
||||||
try:
|
try:
|
||||||
@@ -350,7 +350,7 @@ def handle_close(args: List[str]) -> bool:
|
|||||||
|
|
||||||
def handle_reply(args: List[str]) -> bool:
|
def handle_reply(args: List[str]) -> bool:
|
||||||
"""Reply to an email."""
|
"""Reply to an email."""
|
||||||
log_operation("reply_email_initiated", {"args": args})
|
json_handler.log_operation("reply_email_initiated", {"args": args})
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
error("Usage: drone @ai_mail reply <message_id> \"your message\"")
|
error("Usage: drone @ai_mail reply <message_id> \"your message\"")
|
||||||
return False
|
return False
|
||||||
@@ -367,7 +367,7 @@ def handle_reply(args: List[str]) -> bool:
|
|||||||
else:
|
else:
|
||||||
error(message)
|
error(message)
|
||||||
if success:
|
if success:
|
||||||
log_operation("email_replied", {"message_id": args[0], "reply_id": reply_id})
|
json_handler.log_operation("email_replied", {"message_id": args[0], "reply_id": reply_id})
|
||||||
return success
|
return success
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[email] Reply failed: {e}")
|
logger.error(f"[email] Reply failed: {e}")
|
||||||
@@ -377,7 +377,7 @@ def handle_reply(args: List[str]) -> bool:
|
|||||||
|
|
||||||
def handle_sent(args: List[str]) -> bool:
|
def handle_sent(args: List[str]) -> bool:
|
||||||
"""View sent messages."""
|
"""View sent messages."""
|
||||||
log_operation("sent_viewed")
|
json_handler.log_operation("sent_viewed")
|
||||||
try:
|
try:
|
||||||
sent_folder = Path(get_current_user()["mailbox_path"]) / "sent"
|
sent_folder = Path(get_current_user()["mailbox_path"]) / "sent"
|
||||||
if not sent_folder.exists():
|
if not sent_folder.exists():
|
||||||
@@ -402,7 +402,7 @@ def handle_sent(args: List[str]) -> bool:
|
|||||||
|
|
||||||
def handle_contacts(args: List[str]) -> bool:
|
def handle_contacts(args: List[str]) -> bool:
|
||||||
"""View contacts."""
|
"""View contacts."""
|
||||||
log_operation("contacts_viewed")
|
json_handler.log_operation("contacts_viewed")
|
||||||
try:
|
try:
|
||||||
branches = get_all_branches()
|
branches = get_all_branches()
|
||||||
if not branches:
|
if not branches:
|
||||||
|
|||||||
Reference in New Issue
Block a user