package gates import ( "fmt" "strings" "time" "agent-native/runtime/reminders" ) // ProgressReminder 是进度提醒的节流状态机与格式化机制。 // 计数(第几个工具轮)由调用方按自己的事件流维护;本机制负责 // 「达到阈值后的去重、封顶与文案」。 type ProgressReminder struct { Gap int // 连续多少个工具轮未更新即提醒;0 = ReminderToolGap Cap int // 每 run 提醒上限;0 = MaxChecklistReminders notes *reminders.Bus sent int } func (r *ProgressReminder) gap() int { if r.Gap <= 0 { return ReminderToolGap } return r.Gap } func (r *ProgressReminder) cap() int { if r.Cap <= 0 { return MaxChecklistReminders } return r.Cap } // Sent 报告已发出的提醒条数(调用方判定封顶用)。 func (r *ProgressReminder) Sent() int { return r.sent } // Remind 在给定未完成项时发一条提醒;节流或没有内容时返回 nil。 // 文案与产品现网一致:「计划和待办已连续 N 个工具轮没有更新」。 func (r *ProgressReminder) Remind(todos, plans []string, now time.Time) []string { if r.sent >= r.cap() { return nil } text := FormatUnfinished(r.gap(), todos, plans) if text == "" { return nil } if r.notes == nil { r.notes = reminders.New(0, r.cap()) } key := fmt.Sprintf("%d:%s", r.sent, text) if !r.notes.Admit(reminders.Note{Kind: reminders.KindTodo, Key: key, Text: text}, now) { return nil } r.sent++ return []string{text} } // FormatUnfinished 渲染未完成项提醒;无内容时返回空串。 func FormatUnfinished(gap int, todos, plans []string) string { var lines []string for _, item := range todos { if text := strings.TrimSpace(item); text != "" { lines = append(lines, "- 待办:"+text) } } for _, step := range plans { if text := strings.TrimSpace(step); text != "" { lines = append(lines, "- 计划:"+text) } } if len(lines) == 0 { return "" } return fmt.Sprintf("计划和待办已连续 %d 个工具轮没有更新。未完成项:\n", gap) + strings.Join(lines, "\n") + "\n请更新计划或待办,或在回复里说明为什么先不做。" }