From ab9f7da592fbcca1925d263de44cf240e68351f5 Mon Sep 17 00:00:00 2001 From: Yanxin Lu Date: Mon, 23 Mar 2026 14:27:28 -0700 Subject: [PATCH] calendar: remove self-notification emails (CalDAV sync replaces them) Remove three redundant email flows since all devices sync via CalDAV: - Auto-adding mail@luyx.org as attendee on outgoing invites - Forwarding invites to mail@luyx.org on accept/tentative - Emailing todos to mail@luyx.org on todo add --- MEMORY.md | 6 ++-- TOOLS.md | 3 +- skills/calendar/SKILL.md | 12 ++------ skills/calendar/TESTING.md | 10 ++---- skills/calendar/scripts/cal_tool.py | 47 ++--------------------------- 5 files changed, 12 insertions(+), 66 deletions(-) diff --git a/MEMORY.md b/MEMORY.md index 8f86471..e794b28 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -5,7 +5,7 @@ _这份文件记录持续性项目和重要状态,跨会话保留。_ ## 📝 重要规则 ### 邮件发送规则(v2) -- **youlu@luyanxin.com → mail@luyx.org**: 直接发送,无需确认(用户 SimpleLogin 别名,日历邀请自动抄送) +- **youlu@luyanxin.com → mail@luyx.org**: 直接发送,无需确认(用户 SimpleLogin 别名) - 其他所有对外邮件: 仍需确认 ### 代码审查规则 @@ -88,8 +88,8 @@ _这份文件记录持续性项目和重要状态,跨会话保留。_ - 运行方式: `uv run`(依赖 `icalendar` 库) **功能**: -- 发送日历邀请(自动添加 mail@luyx.org 为参与者) -- 接受/拒绝/暂定回复邀请(自动转发给 mail@luyx.org) +- 发送日历邀请给外部收件人 +- 接受/拒绝/暂定回复邀请(回复给 organizer) - VTODO 待办管理(add/list/complete/delete/check) - 发送/回复/待办操作后自动 `vdirsyncer sync` 同步到 CalDAV - 心跳定期同步日历 diff --git a/TOOLS.md b/TOOLS.md index 7b0b429..56824b3 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -154,8 +154,7 @@ khal list today 7d **支持操作**: 发送邀请 (`send`)、接受/拒绝/暂定 (`reply`)、待办管理 (`todo add/list/complete/delete/check`) **依赖**: himalaya(邮件)、vdirsyncer(CalDAV 同步)、khal(查看日历)、todoman(待办管理) **同步**: 发送/回复后自动 `vdirsyncer sync`,心跳也会定期同步 -**自动抄送**: mail@luyx.org(用户别名)自动加入所有邀请 -**注意**: 发送日历邀请属于对外邮件,除 mail@luyx.org 外需先确认 +**注意**: 发送日历邀请属于对外邮件,需先确认 --- diff --git a/skills/calendar/SKILL.md b/skills/calendar/SKILL.md index 42a1fe8..e6e2630 100644 --- a/skills/calendar/SKILL.md +++ b/skills/calendar/SKILL.md @@ -26,12 +26,6 @@ Calendar invites are outbound emails. Follow the workspace email rules: - **youlu@luyanxin.com -> mail@luyx.org**: send directly, no confirmation needed - **All other recipients**: confirm with user before sending -## Owner Auto-Attendee - -When sending invites, `mail@luyx.org` (owner's SimpleLogin alias) is **always added as an attendee automatically**. All invites include a **1-day reminder** (VALARM) by default. This ensures the owner receives every invite and can Accept/Decline from their own email client. No need to include it in `--to` — it's added by the script. - -When accepting or tentatively accepting a received invite, the original invite is **automatically forwarded to `mail@luyx.org`** so the event lands on the owner's calendar too. - ## Usage All commands go through the wrapper script: @@ -167,7 +161,7 @@ Manage tasks as RFC 5545 VTODO components, stored in `~/.openclaw/workspace/cale ### Sync Model -The agent's local CalDAV is the **source of truth** (no two-way sync). When a todo is created, it's saved locally and emailed to `mail@luyx.org` as a delivery copy. When the user completes a task, they tell the agent, and the agent runs `todo complete`. The daily `todo check` cron reads from local files. +The agent's local CalDAV is the **source of truth**. When a todo is created, it's saved locally and synced to Migadu CalDAV via vdirsyncer — all connected devices (DAVx5, etc.) pick it up automatically. When the user completes a task, they tell the agent, and the agent runs `todo complete`. The daily `todo check` cron reads from local files. ### Priority Mapping (RFC 5545) @@ -195,7 +189,7 @@ $SKILL_DIR/scripts/calendar.sh todo add \ | `--priority` | No | `high`, `medium`, or `low` (default: `medium`) | | `--description` | No | Notes / description | | `--alarm` | No | Reminder trigger: `1d`, `2h`, `30m` (default: `1d`) | -| `--dry-run` | No | Preview ICS + email without saving | +| `--dry-run` | No | Preview ICS without saving | ### `todo list` — List Todos @@ -246,7 +240,7 @@ Same as `todo list` but only NEEDS-ACTION items. Exits silently when no pending 6. Runs `vdirsyncer sync` to push changes to Migadu CalDAV **Managing todos:** -1. `todo add`: Creates a VTODO ICS file (via `icalendar` library), saves to `calendars/tasks/`, emails to owner, syncs +1. `todo add`: Creates a VTODO ICS file (via `icalendar` library), saves to `calendars/tasks/`, syncs to CalDAV 2. `todo list/complete/delete/check`: Delegates to `todoman` CLI for robust RFC 5545 VTODO parsing 3. Runs `vdirsyncer sync` after mutations to push changes to Migadu CalDAV diff --git a/skills/calendar/TESTING.md b/skills/calendar/TESTING.md index f785786..683947b 100644 --- a/skills/calendar/TESTING.md +++ b/skills/calendar/TESTING.md @@ -16,7 +16,7 @@ TEST_DATE=$(date -d "+3 days" +%Y-%m-%d) Generates the ICS and MIME email without sending. Check that: - ICS has `METHOD:REQUEST` - MIME has `Content-Type: text/calendar; method=REQUEST` -- `mail@luyx.org` appears as attendee (auto-added) +- Only `--to` recipients appear as attendees - Times and timezone look correct ```bash @@ -89,8 +89,7 @@ $SKILL_DIR/scripts/calendar.sh reply \ **Verify:** - [ ] Reply sent to organizer (youlu@luyanxin.com, i.e. ourselves) -- [ ] Original invite forwarded to `mail@luyx.org` -- [ ] Event still in `~/.openclaw/workspace/calendars/home/` +- [ ] Event saved to `~/.openclaw/workspace/calendars/home/` - [ ] `vdirsyncer sync` ran - [ ] `khal list "$TEST_DATE"` still shows the event @@ -119,7 +118,6 @@ $SKILL_DIR/scripts/calendar.sh reply \ **Verify:** - [ ] Reply sent to organizer with comment -- [ ] Event NOT forwarded to `mail@luyx.org` - [ ] Event removed from local calendar - [ ] `khal list "$TEST_DATE"` does NOT show "Decline Test Event" @@ -142,12 +140,11 @@ khal list today 7d ## 7. Dry Run: Add Todo -Generates the VTODO ICS and MIME email without saving. Check that: +Generates the VTODO ICS without saving. Check that: - ICS has `BEGIN:VTODO` - ICS has correct `PRIORITY` value (1 for high) - ICS has `STATUS:NEEDS-ACTION` - ICS has `BEGIN:VALARM` -- MIME has `Content-Type: text/calendar` ```bash $SKILL_DIR/scripts/calendar.sh todo add \ @@ -171,7 +168,6 @@ $SKILL_DIR/scripts/calendar.sh todo add \ - [ ] Script exits without error - [ ] `.ics` file created in `~/.openclaw/workspace/calendars/tasks/` - [ ] `todo list` (todoman directly) shows "Test Todo" -- [ ] Email arrives at `mail@luyx.org` with .ics attachment - [ ] `vdirsyncer sync` ran ## 9. List Todos diff --git a/skills/calendar/scripts/cal_tool.py b/skills/calendar/scripts/cal_tool.py index 906ea10..d388972 100644 --- a/skills/calendar/scripts/cal_tool.py +++ b/skills/calendar/scripts/cal_tool.py @@ -34,7 +34,6 @@ from icalendar import Alarm, Calendar, Event, Todo, vCalAddress, vText DEFAULT_TIMEZONE = "America/Los_Angeles" DEFAULT_FROM = "youlu@luyanxin.com" -DEFAULT_OWNER_EMAIL = "mail@luyx.org" # Always added as attendee CALENDAR_DIR = Path.home() / ".openclaw" / "workspace" / "calendars" / "home" TASKS_DIR = Path.home() / ".openclaw" / "workspace" / "calendars" / "tasks" PRODID = "-//OpenClaw//Calendar//EN" @@ -148,12 +147,7 @@ def cmd_send(args): recipients = [addr.strip() for addr in args.to.split(",")] - # Always include owner as attendee - all_attendees = list(recipients) - if DEFAULT_OWNER_EMAIL not in all_attendees: - all_attendees.append(DEFAULT_OWNER_EMAIL) - - for addr in all_attendees: + for addr in recipients: event.add("attendee", f"mailto:{addr}", parameters={ "ROLE": "REQ-PARTICIPANT", "RSVP": "TRUE", @@ -176,11 +170,8 @@ def cmd_send(args): if args.description: body += f"\n\n{args.description}" - # Email goes to all attendees (including owner) - all_to = ", ".join(all_attendees) - # Build MIME email - email_str = _build_calendar_email(args.sender, all_to, args.subject, body, ics_bytes, method="REQUEST") + email_str = _build_calendar_email(args.sender, ", ".join(recipients), args.subject, body, ics_bytes, method="REQUEST") if args.dry_run: print("=== ICS Content ===") @@ -352,20 +343,6 @@ def cmd_reply(args): _send_email(email_str, args.account) print(f"Calendar invite {partstat.lower()}: {summary} (replied to {organizer_email})") - # Forward invite to owner on accept/tentative - if partstat in ("ACCEPTED", "TENTATIVE"): - fwd_body = f"{prefix}: {summary}" - fwd_email = _build_calendar_email( - args.sender, DEFAULT_OWNER_EMAIL, - f"{prefix}: {summary}", fwd_body, - ics_path.read_bytes(), method="REQUEST", - ) - try: - _send_email(fwd_email, args.account) - print(f"Forwarded invite to {DEFAULT_OWNER_EMAIL}") - except subprocess.CalledProcessError: - print(f"Warning: Failed to forward invite to {DEFAULT_OWNER_EMAIL}") - # Save to / remove from local calendar if CALENDAR_DIR.is_dir(): dest = CALENDAR_DIR / f"{uid}.ics" @@ -526,24 +503,11 @@ def cmd_todo_add(args): cal.add_component(todo) ics_bytes = cal.to_ical() - # Build email body prio_label = PRIORITY_LABELS.get(priority, "中") - body = f"待办事项: {args.summary}\n截止日期: {due_date}\n优先级: {prio_label}" - if args.description: - body += f"\n\n{args.description}" - - # Build MIME email - email_str = _build_calendar_email( - DEFAULT_FROM, DEFAULT_OWNER_EMAIL, - f"📋 待办: {args.summary}", - body, ics_bytes, method="REQUEST", - ) if args.dry_run: print("=== ICS Content ===") print(ics_bytes.decode()) - print("=== Email Message ===") - print(email_str) return # Save to TASKS_DIR (without METHOD for CalDAV) @@ -555,13 +519,6 @@ def cmd_todo_add(args): # Sync _sync_calendar() - # Email the VTODO to owner - try: - _send_email(email_str) - print(f"Emailed todo to {DEFAULT_OWNER_EMAIL}") - except subprocess.CalledProcessError: - print(f"Warning: Failed to email todo to {DEFAULT_OWNER_EMAIL}") - def _format_todo_digest(todos): """Format todos into the Chinese priority-grouped digest. Returns string or None."""