package models import ( "strings" "time" ) // CalendarEvent 日历事件。 // // 设计参照 Outlook:事件有时间、提醒、收件人,触发时产生一封邮件。 // 事件本身是日历实体,提醒是触发器,邮件是投递通道 —— 三者分离。 type CalendarEvent struct { EventID string `json:"event_id"` Title string `json:"title"` Description string `json:"description"` ReminderText string `json:"reminder_text"` // AgentName / ToAddress 是**单收件人时代的字段**,保留作兼容与兜底: // Recipients 为空时用它们。新代码一律读 EffectiveRecipients()。 AgentName string `json:"agent_name"` ToAddress string `json:"to_address"` // Recipients 是完整的收件人列表(每项是完整三维地址串)。 // // 为什么不用 []Address 而用 []string:地址的三段语义(尤其 session 位的 // new/别名三态)在**触发那一刻**才该被解析 —— 存结构化的话, // 「.new」这种一次性语义在建事件时就被固化,而重复事件每次触发都该 // 重新决定落到哪条会话。存原始串让 ParseAddress 在投递时做这个决定。 Recipients []string `json:"recipients"` // DeliveryMode 决定多收件人怎么投: // "separate"(默认)—— 每人各发一封,落在各自的会话里,互相看不到 // "together" —— 第一个是主收件人,其余进 cc_list,共享同一条线索 // // 两种语义都需要而不是二选一:「让三个 Agent 各自独立汇报」与 // 「让 pi 主办、dsh 知情」是完全不同的任务形态,用错会让协作失败 —— // 前者用 together 会让三个 Agent 互相看到对方的回复而趋同, // 后者用 separate 会让 dsh 完全不知道 pi 在做什么。 DeliveryMode string `json:"delivery_mode"` EventTime time.Time `json:"event_time"` RemindBefore int `json:"remind_before"` // 提前多少分钟 // Recurrence:公历 none/daily/weekly/monthly/yearly + 农历两种 // lunar_monthly —— 每农历月同一日(如每月十五) // lunar_yearly —— 每农历年同月同日(过农历生日/祭日) // // lunar_daily 不存在:农历的「日」与公历同长,那就是 daily。 // lunar_weekly 也不存在:农历没有「周」这个单位。 Recurrence string `json:"recurrence"` RecurrenceEnd *time.Time `json:"recurrence_end,omitempty"` Status string `json:"status"` // active/paused/cancelled LastFiredAt *time.Time `json:"last_fired_at,omitempty"` // PermissionMode 是事件触发时新建会话应采用的档位(plan / workspace / full)。 // 空 = workspace(默认)。 // 复用已有会话时不能直接搬用:要 ModeAtMost(会话现档, 事件档) —— // 事件档表示「这件事允许到什么程度」,而会话现档可能更严(plan 档派出的 // 任务不该因为日程触发就偷偷升到 workspace)。 PermissionMode string `json:"permission_mode"` // FiredFor 是已触发的那个 occurrence(值 = 当时的 EventTime)。 // 去重靠它与 EventTime 相等判断,不是拿 LastFiredAt 比大小 —— // DueEvents 有 60 秒 lookahead,后者在窗口内恒为真会导致每 tick 重发。 FiredFor *time.Time `json:"fired_for,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` CreatedBy string `json:"created_by"` } // 重复规则常量。农历规则单独一组:它们的推进要经过 internal/lunar, // 不能像公历那样 AddDate 固定天数(农历月 29~30 天、闰年 13 个月)。 const ( RecurNone = "none" RecurDaily = "daily" RecurWeekly = "weekly" RecurMonthly = "monthly" RecurYearly = "yearly" RecurLunarMonthly = "lunar_monthly" RecurLunarYearly = "lunar_yearly" ) // IsLunarRecurrence 判断一条重复规则是否按农历推进。 func IsLunarRecurrence(r string) bool { return r == RecurLunarMonthly || r == RecurLunarYearly } // 事件状态常量。 // // 此前这三个值只以裸字符串形式散落在 handler、scheduler 与前端里,而更新端点 // 把 `status` 原样写进库 —— 于是一个拼错的值(比如 "pause")会变成一个 // **调度器不认识的状态**:DueEvents 只查 status='active',那条提醒于是静默失效。 // 人以为自己只是暂停了它,实际上再也恢复不了(界面的下拉框里没有这个选项)。 // // 提成常量后,handler.validEventStatus 能对着这一份清单校验。 const ( // EventActive 生效中:到点会触发提醒。 EventActive = "active" // EventPaused 暂停:保留事件与重复规则,但不触发。 EventPaused = "paused" // EventCancelled 已取消:保留历史记录,不再触发也不再推进重复。 EventCancelled = "cancelled" ) // ValidEventStatus 判断状态取值是否合法。 func ValidEventStatus(s string) bool { switch s { case EventActive, EventPaused, EventCancelled: return true } return false } // 投递模式常量。 const ( DeliverSeparate = "separate" DeliverTogether = "together" ) // EffectiveRecipients 返回真正要投的收件人列表。 // // Recipients 优先;为空时退回 ToAddress,再退回 AgentName。 // 这个兜底链让旧数据(只有 agent_name 的事件)继续工作 —— // 历史事件不迁移,读的时候归一化。 func (e *CalendarEvent) EffectiveRecipients() []string { if len(e.Recipients) > 0 { out := make([]string, 0, len(e.Recipients)) for _, r := range e.Recipients { if r = strings.TrimSpace(r); r != "" { out = append(out, r) } } if len(out) > 0 { return out } } if a := strings.TrimSpace(e.ToAddress); a != "" { return []string{a} } if a := strings.TrimSpace(e.AgentName); a != "" { return []string{a} } return nil } // EffectiveDeliveryMode 归一化投递模式,未知值按 separate 处理。 // // 默认 separate 而不是 together:separate 的失败是「Agent 各干各的」, // together 的失败是「本该独立的 Agent 互相污染了上下文」—— // 后者更难发现也更难挽回。 func (e *CalendarEvent) EffectiveDeliveryMode() string { if e.DeliveryMode == DeliverTogether { return DeliverTogether } return DeliverSeparate } // CalendarAttachment 事件附件。 type CalendarAttachment struct { AttachmentID string `json:"attachment_id"` EventID string `json:"event_id"` Filename string `json:"filename"` SHA256 string `json:"sha256"` SizeBytes int64 `json:"size_bytes"` CreatedAt time.Time `json:"created_at"` } // CalendarView 日历视图(月/周/日)。 type CalendarView struct { Events []CalendarEvent `json:"events"` // 当前时间线上的事件数(用于统计徽章) UpcomingCount int `json:"upcoming_count"` TodayCount int `json:"today_count"` }