calendar: add contacts system to prevent recipient address hallucination
Agent hallucinated "xiaojuzi@meta.com" instead of looking up the correct address from USER.md. Prompting rules can't reliably prevent this, so recipient validation is now enforced at the tool level. - Add contact list/add/delete subcommands (vCard .vcf files synced via CardDAV) - send --to now resolves through contacts: accepts names, name:type, or known emails; rejects unknown addresses with available contacts shown - Multi-email contacts supported with type qualifier (e.g. "小橘子:work") - Adding contacts and sending are separate operations (no chaining)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# Testing the Calendar Skill
|
||||
|
||||
End-to-end tests for send, reply, todo, calendar sync, and local calendar. All commands use `--dry-run` first, then live.
|
||||
End-to-end tests for contacts, send, reply, todo, calendar sync, and local calendar. All commands use `--dry-run` first, then live.
|
||||
|
||||
**Important**: Tests 1-3 (contacts) must run first — `send` requires recipients to be in the contacts list.
|
||||
|
||||
```bash
|
||||
SKILL_DIR=~/.openclaw/workspace/skills/calendar
|
||||
@@ -11,7 +13,118 @@ TEST_DATE=$(date -d "+3 days" +%Y-%m-%d)
|
||||
|
||||
---
|
||||
|
||||
## 1. Dry Run: Send Invite
|
||||
## 1. Contact Add and List
|
||||
|
||||
Set up test contacts needed for send tests.
|
||||
|
||||
```bash
|
||||
# Add a contact with a single email
|
||||
$SKILL_DIR/scripts/calendar.sh contact add --name "测试用户" --email "mail@luyx.org"
|
||||
|
||||
# List contacts
|
||||
$SKILL_DIR/scripts/calendar.sh contact list
|
||||
|
||||
# Add a contact with typed email and nickname
|
||||
$SKILL_DIR/scripts/calendar.sh contact add --name "测试多邮箱" --email "work@example.com" --type work --nickname "多邮箱"
|
||||
|
||||
# Add a second email to the same contact
|
||||
$SKILL_DIR/scripts/calendar.sh contact add --name "测试多邮箱" --email "home@example.com" --type home
|
||||
|
||||
# List again — should show both emails
|
||||
$SKILL_DIR/scripts/calendar.sh contact list
|
||||
```
|
||||
|
||||
**Verify:**
|
||||
- [ ] `contact add` prints "Added contact: ..."
|
||||
- [ ] Second `contact add` prints "Updated contact: ... — added ..."
|
||||
- [ ] `contact list` shows all contacts with email types
|
||||
- [ ] `.vcf` files created in `~/.openclaw/workspace/contacts/default/`
|
||||
|
||||
## 2. Recipient Resolution (Send Validation)
|
||||
|
||||
Test that `send --to` correctly resolves contacts and rejects unknown addresses.
|
||||
|
||||
```bash
|
||||
# Name resolves (single email contact) — should work
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "测试用户" \
|
||||
--subject "Resolve Test" --summary "Resolve Test" \
|
||||
--start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \
|
||||
--dry-run
|
||||
|
||||
# Name:type resolves — should work
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "测试多邮箱:work" \
|
||||
--subject "Resolve Test" --summary "Resolve Test" \
|
||||
--start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \
|
||||
--dry-run
|
||||
|
||||
# Nickname resolves — should work
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "多邮箱:home" \
|
||||
--subject "Resolve Test" --summary "Resolve Test" \
|
||||
--start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \
|
||||
--dry-run
|
||||
|
||||
# Known raw email resolves — should work
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--subject "Resolve Test" --summary "Resolve Test" \
|
||||
--start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \
|
||||
--dry-run
|
||||
|
||||
# Unknown email REJECTED — should FAIL
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "xiaojuzi@meta.com" \
|
||||
--subject "Resolve Test" --summary "Resolve Test" \
|
||||
--start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \
|
||||
--dry-run
|
||||
|
||||
# Multi-email without type REJECTED — should FAIL (ambiguous)
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "测试多邮箱" \
|
||||
--subject "Resolve Test" --summary "Resolve Test" \
|
||||
--start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \
|
||||
--dry-run
|
||||
|
||||
# Unknown name REJECTED — should FAIL
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "不存在的人" \
|
||||
--subject "Resolve Test" --summary "Resolve Test" \
|
||||
--start "${TEST_DATE}T15:00:00" --end "${TEST_DATE}T16:00:00" \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
**Verify:**
|
||||
- [ ] First 4 commands succeed (show ICS output)
|
||||
- [ ] Unknown email fails with "not found in contacts" + available contacts list
|
||||
- [ ] Multi-email without type fails with "has multiple emails. Specify type"
|
||||
- [ ] Unknown name fails with "not found" + available contacts list
|
||||
|
||||
## 3. Contact Delete
|
||||
|
||||
```bash
|
||||
# Delete the multi-email test contact
|
||||
$SKILL_DIR/scripts/calendar.sh contact delete --name "测试多邮箱"
|
||||
|
||||
# Verify it's gone
|
||||
$SKILL_DIR/scripts/calendar.sh contact list
|
||||
|
||||
# Delete by nickname — should fail (contact already deleted)
|
||||
$SKILL_DIR/scripts/calendar.sh contact delete --name "多邮箱"
|
||||
```
|
||||
|
||||
**Verify:**
|
||||
- [ ] Delete prints "Deleted contact: 测试多邮箱"
|
||||
- [ ] `contact list` no longer shows that contact
|
||||
- [ ] Second delete fails with "No contact matching"
|
||||
- [ ] `.vcf` file removed from contacts dir
|
||||
|
||||
---
|
||||
|
||||
## 4. Dry Run: Send Invite
|
||||
|
||||
**Prerequisite**: "测试用户" contact from test 1 must exist.
|
||||
|
||||
Generates the ICS and MIME email without sending. Check that:
|
||||
- ICS has `METHOD:REQUEST`
|
||||
@@ -23,7 +136,7 @@ Generates the ICS and MIME email without sending. Check that:
|
||||
```bash
|
||||
# Default alarm (1 day before)
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--to "测试用户" \
|
||||
--subject "Test Invite" \
|
||||
--summary "Test Event" \
|
||||
--start "${TEST_DATE}T15:00:00" \
|
||||
@@ -32,7 +145,7 @@ $SKILL_DIR/scripts/calendar.sh send \
|
||||
|
||||
# Custom alarm (1 hour before)
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--to "测试用户" \
|
||||
--subject "Test Invite (1h alarm)" \
|
||||
--summary "Test Event (1h alarm)" \
|
||||
--start "${TEST_DATE}T15:00:00" \
|
||||
@@ -41,13 +154,13 @@ $SKILL_DIR/scripts/calendar.sh send \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## 2. Live Send: Self-Invite
|
||||
## 5. Live Send: Self-Invite
|
||||
|
||||
Send a real invite to `mail@luyx.org` only (no confirmation needed per email rules).
|
||||
|
||||
```bash
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--to "测试用户" \
|
||||
--subject "Calendar Skill Test" \
|
||||
--summary "Calendar Skill Test" \
|
||||
--start "${TEST_DATE}T15:00:00" \
|
||||
@@ -61,7 +174,7 @@ $SKILL_DIR/scripts/calendar.sh send \
|
||||
- [ ] Email shows Accept/Decline/Tentative buttons (not just an attachment)
|
||||
- [ ] `.ics` file saved to `~/.openclaw/workspace/calendars/home/`
|
||||
|
||||
## 3. Verify Calendar Sync and Local Calendar
|
||||
## 6. Verify Calendar Sync and Local Calendar
|
||||
|
||||
After sending in step 2, check that the event synced and appears locally.
|
||||
|
||||
@@ -82,7 +195,7 @@ khal list "$TEST_DATE"
|
||||
- [ ] `.ics` file exists in `~/.openclaw/workspace/calendars/home/`
|
||||
- [ ] `khal list` shows "Calendar Skill Test" on the test date
|
||||
|
||||
## 4. Reply: Accept the Self-Invite
|
||||
## 7. Reply: Accept the Self-Invite
|
||||
|
||||
The invite sent in step 2 should be in the inbox. Find it, then accept it. This tests the full reply flow without needing an external sender.
|
||||
|
||||
@@ -105,14 +218,14 @@ $SKILL_DIR/scripts/calendar.sh reply \
|
||||
- [ ] `vdirsyncer sync` ran
|
||||
- [ ] `khal list "$TEST_DATE"` still shows the event
|
||||
|
||||
## 5. Reply: Decline an Invite
|
||||
## 8. Reply: Decline an Invite
|
||||
|
||||
Send another self-invite, then decline it. This verifies decline removes the event from local calendar.
|
||||
|
||||
```bash
|
||||
# Send a second test invite
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--to "测试用户" \
|
||||
--subject "Decline Test" \
|
||||
--summary "Decline Test Event" \
|
||||
--start "${TEST_DATE}T17:00:00" \
|
||||
@@ -133,7 +246,7 @@ $SKILL_DIR/scripts/calendar.sh reply \
|
||||
- [ ] Event removed from local calendar
|
||||
- [ ] `khal list "$TEST_DATE"` does NOT show "Decline Test Event"
|
||||
|
||||
## 6. Verify Final Calendar State
|
||||
## 9. Verify Final Calendar State
|
||||
|
||||
After all tests, confirm the calendar is in a clean state.
|
||||
|
||||
@@ -150,7 +263,7 @@ khal list today 7d
|
||||
|
||||
---
|
||||
|
||||
## 7. Dry Run: Add Todo
|
||||
## 10. Dry Run: Add Todo
|
||||
|
||||
Generates the VTODO ICS without saving. Check that:
|
||||
- ICS has `BEGIN:VTODO`
|
||||
@@ -166,7 +279,7 @@ $SKILL_DIR/scripts/calendar.sh todo add \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## 8. Live Add: Create a Todo
|
||||
## 11. Live Add: Create a Todo
|
||||
|
||||
```bash
|
||||
$SKILL_DIR/scripts/calendar.sh todo add \
|
||||
@@ -182,7 +295,7 @@ $SKILL_DIR/scripts/calendar.sh todo add \
|
||||
- [ ] `todo list` (todoman directly) shows "Test Todo"
|
||||
- [ ] `vdirsyncer sync` ran
|
||||
|
||||
## 9. List Todos
|
||||
## 12. List Todos
|
||||
|
||||
```bash
|
||||
# Via our wrapper (formatted Chinese output)
|
||||
@@ -200,7 +313,7 @@ $SKILL_DIR/scripts/calendar.sh todo list --all
|
||||
- [ ] Priority grouping is correct in wrapper output
|
||||
- [ ] `--all` flag works (same output when none are completed)
|
||||
|
||||
## 10. Edit a Todo
|
||||
## 13. Edit a Todo
|
||||
|
||||
Change the due date and priority of the test todo from step 8.
|
||||
|
||||
@@ -248,7 +361,7 @@ $SKILL_DIR/scripts/calendar.sh todo edit --match "Test Todo"
|
||||
# Should print "Nothing to change" message
|
||||
```
|
||||
|
||||
## 11. Complete a Todo
|
||||
## 14. Complete a Todo
|
||||
|
||||
```bash
|
||||
$SKILL_DIR/scripts/calendar.sh todo complete --match "Test Todo"
|
||||
@@ -260,7 +373,7 @@ $SKILL_DIR/scripts/calendar.sh todo complete --match "Test Todo"
|
||||
- [ ] `$SKILL_DIR/scripts/calendar.sh todo list --all` — appears as completed (with checkmark)
|
||||
- [ ] `vdirsyncer sync` ran
|
||||
|
||||
## 12. Delete a Todo
|
||||
## 15. Delete a Todo
|
||||
|
||||
Create a second test todo, then delete it.
|
||||
|
||||
@@ -283,7 +396,7 @@ $SKILL_DIR/scripts/calendar.sh todo delete --match "Delete Me"
|
||||
- [ ] `todo list` (todoman) does not show "Delete Me Todo"
|
||||
- [ ] `vdirsyncer sync` ran
|
||||
|
||||
## 13. Todo Check (Cron Output)
|
||||
## 16. Todo Check (Cron Output)
|
||||
|
||||
```bash
|
||||
# Create a test todo
|
||||
@@ -305,7 +418,7 @@ $SKILL_DIR/scripts/calendar.sh todo check
|
||||
# Should produce no output
|
||||
```
|
||||
|
||||
## 14. Dry Run: Recurring Event (--rrule)
|
||||
## 17. Dry Run: Recurring Event (--rrule)
|
||||
|
||||
Test recurring event generation. Use a date that falls on a Tuesday.
|
||||
|
||||
@@ -314,7 +427,7 @@ Test recurring event generation. Use a date that falls on a Tuesday.
|
||||
NEXT_TUE=$(python3 -c "from datetime import date,timedelta; d=date.today(); d+=timedelta((1-d.weekday())%7 or 7); print(d)")
|
||||
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--to "测试用户" \
|
||||
--subject "Recurring Test (Tue)" \
|
||||
--summary "Recurring Test (Tue)" \
|
||||
--start "${NEXT_TUE}T14:30:00" \
|
||||
@@ -328,14 +441,14 @@ $SKILL_DIR/scripts/calendar.sh send \
|
||||
- [ ] DTSTART falls on a Tuesday
|
||||
- [ ] No validation errors
|
||||
|
||||
## 15. Validation: DTSTART/BYDAY Mismatch
|
||||
## 18. Validation: DTSTART/BYDAY Mismatch
|
||||
|
||||
Verify the tool rejects mismatched DTSTART and BYDAY.
|
||||
|
||||
```bash
|
||||
# This should FAIL — start is on a Tuesday but BYDAY=TH
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--to "测试用户" \
|
||||
--subject "Mismatch Test" \
|
||||
--summary "Mismatch Test" \
|
||||
--start "${NEXT_TUE}T09:00:00" \
|
||||
@@ -349,7 +462,7 @@ $SKILL_DIR/scripts/calendar.sh send \
|
||||
- [ ] Error message says DTSTART falls on TU but RRULE says BYDAY=TH
|
||||
- [ ] Suggests changing --start to a date that falls on TH
|
||||
|
||||
## 16. Event List
|
||||
## 19. Event List
|
||||
|
||||
```bash
|
||||
# List upcoming events
|
||||
@@ -367,12 +480,12 @@ $SKILL_DIR/scripts/calendar.sh event list --format "{uid} {title}"
|
||||
- [ ] Search narrows results correctly
|
||||
- [ ] UIDs are displayed with --format
|
||||
|
||||
## 17. Event Delete
|
||||
## 20. Event Delete
|
||||
|
||||
```bash
|
||||
# Send a throwaway event first
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--to "测试用户" \
|
||||
--subject "Delete Me Event" \
|
||||
--summary "Delete Me Event" \
|
||||
--start "${TEST_DATE}T20:00:00" \
|
||||
@@ -394,7 +507,7 @@ $SKILL_DIR/scripts/calendar.sh event list --search "Delete Me"
|
||||
- [ ] Other events are untouched
|
||||
- [ ] `vdirsyncer sync` ran after delete
|
||||
|
||||
## 18. Event Delete: Cancel Single Occurrence (EXDATE)
|
||||
## 21. Event Delete: Cancel Single Occurrence (EXDATE)
|
||||
|
||||
Test that `--date` cancels one occurrence of a recurring event without deleting the series.
|
||||
|
||||
@@ -403,7 +516,7 @@ Test that `--date` cancels one occurrence of a recurring event without deleting
|
||||
NEXT_SAT=$(python3 -c "from datetime import date,timedelta; d=date.today(); d+=timedelta((5-d.weekday())%7 or 7); print(d)")
|
||||
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "mail@luyx.org" \
|
||||
--to "测试用户" \
|
||||
--subject "EXDATE Test (Sat)" \
|
||||
--summary "EXDATE Test (Sat)" \
|
||||
--start "${NEXT_SAT}T10:00:00" \
|
||||
@@ -425,7 +538,7 @@ ls ~/.openclaw/workspace/calendars/home/ | grep -i exdate
|
||||
- [ ] `.ics` file still exists in calendar dir
|
||||
- [ ] `khal list` no longer shows the cancelled date but shows subsequent Saturdays
|
||||
|
||||
## 19. Event Delete: Recurring Without --date or --all (Safety Guard)
|
||||
## 22. Event Delete: Recurring Without --date or --all (Safety Guard)
|
||||
|
||||
```bash
|
||||
# Try to delete the recurring event without --date or --all — should FAIL
|
||||
@@ -436,7 +549,7 @@ $SKILL_DIR/scripts/calendar.sh event delete --match "EXDATE Test"
|
||||
- [ ] Script exits with error
|
||||
- [ ] Error message explains the two options: `--date` or `--all`
|
||||
|
||||
## 20. Event Delete: Recurring With --all
|
||||
## 23. Event Delete: Recurring With --all
|
||||
|
||||
```bash
|
||||
# Delete the entire series
|
||||
@@ -447,11 +560,12 @@ $SKILL_DIR/scripts/calendar.sh event delete --match "EXDATE Test" --all
|
||||
- [ ] .ics file is removed
|
||||
- [ ] `event list --search "EXDATE Test"` shows nothing
|
||||
|
||||
## 21. Regression: Existing Invite Commands (was #18)
|
||||
## 24. Regression: Send Rejects Unknown Addresses
|
||||
|
||||
Verify new features didn't break VEVENT flow.
|
||||
Verify that `send` no longer accepts arbitrary email addresses.
|
||||
|
||||
```bash
|
||||
# This MUST fail — raw unknown email should be rejected
|
||||
$SKILL_DIR/scripts/calendar.sh send \
|
||||
--to "test@example.com" \
|
||||
--subject "Regression Test" \
|
||||
@@ -462,9 +576,9 @@ $SKILL_DIR/scripts/calendar.sh send \
|
||||
```
|
||||
|
||||
**Verify:**
|
||||
- [ ] ICS has `BEGIN:VEVENT`, `METHOD:REQUEST`
|
||||
- [ ] No RRULE present (single event)
|
||||
- [ ] No errors
|
||||
- [ ] Command exits with error
|
||||
- [ ] Error shows "not found in contacts" with available contacts list
|
||||
- [ ] No ICS generated
|
||||
|
||||
---
|
||||
|
||||
@@ -507,3 +621,6 @@ todo list
|
||||
| SMTP rate limit / EOF error | Too many sends too fast. Wait 10+ seconds between sends (Migadu limit) |
|
||||
| Events disappeared after cleanup | **Never use `rm *.ics`** on calendar dirs. Use `event delete --match` instead |
|
||||
| Recurring series deleted when cancelling one date | Use `--date YYYY-MM-DD` to add EXDATE, not bare `event delete` (which requires `--all` for recurring) |
|
||||
| `send` rejects email address | Address not in contacts. Add with `contact add` first (separate from send) |
|
||||
| `send` says "has multiple emails" | Contact has work+home emails. Use `name:type` syntax (e.g. `小橘子:work`) |
|
||||
| Contacts dir empty after sync | Check vdirsyncer CardDAV pair is configured for `contacts/default/` |
|
||||
|
||||
Reference in New Issue
Block a user