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
This commit is contained in:
@@ -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
|
||||
- 心跳定期同步日历
|
||||
|
||||
3
TOOLS.md
3
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 外需先确认
|
||||
**注意**: 发送日历邀请属于对外邮件,需先确认
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user