feat(heartbeat): save emails to KB + fix memory symlink access
- heartbeat saves unread whitelisted emails via email_process --save --json - fix: add --add-dir so Claude CLI subprocess can access memory/ symlink - email_check/process: use BODY.PEEK[] to avoid marking emails as read - email_process: simplify credential loading via credential_store only - config: heartbeat interval 30→120min, quiet hours end 08→07 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -144,22 +144,43 @@ def _is_quiet_hour(hour: int, quiet_hours: tuple[int, int]) -> bool:
|
||||
|
||||
|
||||
def _check_email(state: dict) -> str | None:
|
||||
"""Check for new emails via tools/email_check.py. Parses JSON output."""
|
||||
script = TOOLS_DIR / "email_check.py"
|
||||
if not script.exists():
|
||||
"""Save unread whitelisted emails as notes via tools/email_process.py.
|
||||
|
||||
Uses --save --json to process and mark as read only after saving.
|
||||
Falls back to email_check.py for reporting if no emails to save.
|
||||
"""
|
||||
process_script = TOOLS_DIR / "email_process.py"
|
||||
check_script = TOOLS_DIR / "email_check.py"
|
||||
|
||||
# First: save unread emails as notes
|
||||
if process_script.exists():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["python3", str(process_script), "--save", "--json"],
|
||||
capture_output=True, text=True, timeout=60,
|
||||
cwd=str(PROJECT_ROOT)
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
saved = json.loads(result.stdout.strip())
|
||||
ok_saves = [r for r in saved if r.get("ok")]
|
||||
if ok_saves:
|
||||
subjects = [r.get("subject", "?") for r in ok_saves[:5]]
|
||||
return f"Email: {len(ok_saves)} salvate ({', '.join(subjects)})"
|
||||
except Exception as e:
|
||||
log.warning("Email process failed: %s", e)
|
||||
|
||||
# Fallback: just report unread count (without marking as read)
|
||||
if not check_script.exists():
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["python3", str(script)],
|
||||
["python3", str(check_script)],
|
||||
capture_output=True, text=True, timeout=30,
|
||||
cwd=str(PROJECT_ROOT)
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
output = result.stdout.strip()
|
||||
if not output:
|
||||
return None
|
||||
data = json.loads(output)
|
||||
data = json.loads(result.stdout.strip())
|
||||
if not data.get("ok"):
|
||||
return None
|
||||
count = data.get("unread_count", 0)
|
||||
@@ -167,14 +188,7 @@ def _check_email(state: dict) -> str | None:
|
||||
return None
|
||||
emails = data.get("emails", [])
|
||||
subjects = [e.get("subject", "?") for e in emails[:5]]
|
||||
subject_list = ", ".join(subjects)
|
||||
return f"Email: {count} necitite ({subject_list})"
|
||||
except json.JSONDecodeError:
|
||||
# Fallback: treat as plain text
|
||||
output = result.stdout.strip()
|
||||
if output and output != "0":
|
||||
return f"Email: {output}"
|
||||
return None
|
||||
return f"Email: {count} necitite ({', '.join(subjects)})"
|
||||
except Exception as e:
|
||||
log.warning("Email check failed: %s", e)
|
||||
return None
|
||||
@@ -421,10 +435,13 @@ def _run_claude_extra(hb_config: dict, python_results: list[str],
|
||||
)
|
||||
prompt = "\n\n".join(context_parts)
|
||||
|
||||
# Resolve symlink for memory/ so Claude CLI can access it from outside project root
|
||||
memory_real = (PROJECT_ROOT / "memory").resolve()
|
||||
cmd = [
|
||||
CLAUDE_BIN, "-p", prompt,
|
||||
"--model", model,
|
||||
"--output-format", "json",
|
||||
"--add-dir", str(memory_real),
|
||||
]
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user