chore: directory migration - gateway→server, web→client/electron

This commit is contained in:
2026-09-08 19:16:35 +08:00
parent fd9f99a3f9
commit f9d757b5e5
243 changed files with 5095 additions and 228 deletions

View File

@ -0,0 +1,176 @@
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 而不是 togetherseparate 的失败是「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"`
}