* fix(ai_mail): resolve relative mailbox_path bug + archive dead handlers Fix get_user_by_email() and get_all_users() returning relative mailbox_path values from registry instead of absolute paths (causing doubled paths in reply). Added path resolution matching get_current_user() pattern. Archive 7 unused handler files to apps/.archive/ (gitignored): - pending_work.py — planned feature, never wired - lock_utils.py — superseded by inbox_lock.py - data_ops.py — consumer (error_monitor) was archived - config_generator.py, users/load.py, registry/validate.py — scaffolded, never used - trigger/error_handler.py — superseded by trigger branch's event system Added test_user_paths.py (13 tests) covering absolute path resolution. Co-Authored-By: @ai_mail <ai_mail@aipass> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(spawn): seedgo compliance cleanup — 93% to 98% Archive legacy files (dev.local.md, ai_mail.local/, .seed/). Update META Modified dates on 22 files. Update README architecture tree and freshness. Fix 15 Pyright unused warnings. Add introspection no-args gates to 6 modules. Wire json_handler + log_operation to 15 operational files. Add seedgo bypass for 5 pure data files and log_structure. Remove deprecated dev.local.md from builder template. Co-Authored-By: @spawn <spawn@aipass> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(system): session 34 — maintenance day, multi-branch work System changes: - setup.sh: symlink install replaces .bashrc PATH export for drone/seedgo - .claude/settings.json: deny list moved to per-branch settings.local.json - .gitignore: updated - CLAUDE.md culture doc: stale refs fixed (Cortex→spawn, id.json→passport.json) Branch work (accumulated from 15-branch rounds): - daemon: stale term cleanup, plugin removals, json handler modernization - drone: module refactor (discovery, registry, router, resolver, config) - commons: 50+ file cleanup (imports, handlers, modules) - flow: template loader, plan type system, command parser Per-branch settings.local.json: reworked deny lists — allow PR workflow, deny destructive git ops (reset, rebase, merge, clean, force push, rm -rf). Fixed pattern syntax from :* to * glob. Co-Authored-By: @devpulse <devpulse@aipass> --------- Co-authored-by: @ai_mail <ai_mail@aipass> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: @spawn <spawn@aipass> Co-authored-by: @devpulse <devpulse@aipass>
108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
# =================== AIPass ====================
|
|
# Name: data_loader.py
|
|
# Description: DAEMON Data Loading Handler
|
|
# Version: 1.0.0
|
|
# Created: 2026-01-29
|
|
# Modified: 2026-01-29
|
|
# =============================================
|
|
|
|
"""
|
|
Handler for loading DAEMON data from inbox and local files.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Dict, Any, List
|
|
|
|
from aipass.daemon.apps.handlers.json import json_handler
|
|
|
|
# =============================================
|
|
# CONSTANTS
|
|
# =============================================
|
|
|
|
_DAEMON_ROOT = Path(__file__).resolve().parents[3] # src/aipass/daemon/
|
|
INBOX_PATH = _DAEMON_ROOT / "ai_mail.local" / "inbox.json"
|
|
LOCAL_PATH = _DAEMON_ROOT / "DAEMON.local.json"
|
|
|
|
# =============================================
|
|
# DATA LOADING
|
|
# =============================================
|
|
|
|
def load_inbox() -> Dict[str, Any]:
|
|
"""Load inbox.json and return parsed data."""
|
|
json_handler.log_operation("data_loaded")
|
|
if not INBOX_PATH.exists():
|
|
return {"messages": [], "total_messages": 0, "unread_count": 0}
|
|
|
|
try:
|
|
with open(INBOX_PATH, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
return {"messages": [], "total_messages": 0, "unread_count": 0}
|
|
|
|
|
|
def load_local() -> Dict[str, Any]:
|
|
"""Load DAEMON.local.json and return parsed data."""
|
|
if not LOCAL_PATH.exists():
|
|
return {"sessions": [], "active_tasks": {}}
|
|
|
|
try:
|
|
with open(LOCAL_PATH, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
return {"sessions": [], "active_tasks": {}}
|
|
|
|
|
|
# =============================================
|
|
# DIGEST ANALYSIS
|
|
# =============================================
|
|
|
|
def categorize_messages(messages: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
|
|
"""
|
|
Categorize inbox messages by status.
|
|
|
|
Returns:
|
|
Dict with keys: new, opened, actionable, informational
|
|
"""
|
|
categories: Dict[str, List[Dict[str, Any]]] = {
|
|
"new": [],
|
|
"opened": [],
|
|
"actionable": [],
|
|
"informational": []
|
|
}
|
|
|
|
for msg in messages:
|
|
status = msg.get("status", "new")
|
|
subject = msg.get("subject", "").upper()
|
|
|
|
if status == "new":
|
|
categories["new"].append(msg)
|
|
elif status == "opened":
|
|
categories["opened"].append(msg)
|
|
|
|
if any(kw in subject for kw in ["TASK:", "BUILD:", "FIX:", "PROPOSAL:", "REQUEST:"]):
|
|
categories["actionable"].append(msg)
|
|
elif any(kw in subject for kw in ["INFO", "RE:", "FYI", "NOTIFICATION"]):
|
|
categories["informational"].append(msg)
|
|
|
|
return categories
|
|
|
|
|
|
def get_session_summary(local_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Extract session summary from local.json."""
|
|
sessions = local_data.get("sessions", [])
|
|
active_tasks = local_data.get("active_tasks", {})
|
|
|
|
return {
|
|
"total_sessions": len(sessions),
|
|
"today_focus": active_tasks.get("today_focus", "None"),
|
|
"recently_completed": active_tasks.get("recently_completed", []),
|
|
"latest_session": sessions[0] if sessions else None
|
|
}
|
|
|
|
|
|
def get_escalations(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
"""Find messages that need escalation."""
|
|
return [m for m in messages if "BLOCKED" in m.get("subject", "").upper()
|
|
or "URGENT" in m.get("subject", "").upper()]
|