mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-19 02:28:59 +08:00
task history retention: add 3-day configurable cleanup and notify allowlist enforcement
This commit is contained in:
@@ -950,6 +950,7 @@ func buildAutonomyEngine(cfg *config.Config, msgBus *bus.MessageBus) *autonomy.E
|
|||||||
QuietHours: a.QuietHours,
|
QuietHours: a.QuietHours,
|
||||||
UserIdleResumeSec: a.UserIdleResumeSec,
|
UserIdleResumeSec: a.UserIdleResumeSec,
|
||||||
MaxRoundsWithoutUser: a.MaxRoundsWithoutUser,
|
MaxRoundsWithoutUser: a.MaxRoundsWithoutUser,
|
||||||
|
TaskHistoryRetentionDays: a.TaskHistoryRetentionDays,
|
||||||
WaitingResumeDebounceSec: a.WaitingResumeDebounceSec,
|
WaitingResumeDebounceSec: a.WaitingResumeDebounceSec,
|
||||||
AllowedTaskKeywords: a.AllowedTaskKeywords,
|
AllowedTaskKeywords: a.AllowedTaskKeywords,
|
||||||
ImportantKeywords: cfg.Agents.Defaults.Texts.AutonomyImportantKeywords,
|
ImportantKeywords: cfg.Agents.Defaults.Texts.AutonomyImportantKeywords,
|
||||||
|
|||||||
@@ -24,7 +24,10 @@
|
|||||||
"notify_same_reason_cooldown_sec": 900,
|
"notify_same_reason_cooldown_sec": 900,
|
||||||
"quiet_hours": "23:00-08:00",
|
"quiet_hours": "23:00-08:00",
|
||||||
"user_idle_resume_sec": 20,
|
"user_idle_resume_sec": 20,
|
||||||
|
"max_rounds_without_user": 12,
|
||||||
|
"task_history_retention_days": 3,
|
||||||
"waiting_resume_debounce_sec": 5,
|
"waiting_resume_debounce_sec": 5,
|
||||||
|
"allowed_task_keywords": [],
|
||||||
"notify_channel": "",
|
"notify_channel": "",
|
||||||
"notify_chat_id": "",
|
"notify_chat_id": "",
|
||||||
"notify_allow_chats": []
|
"notify_allow_chats": []
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type Options struct {
|
|||||||
UserIdleResumeSec int
|
UserIdleResumeSec int
|
||||||
WaitingResumeDebounceSec int
|
WaitingResumeDebounceSec int
|
||||||
MaxRoundsWithoutUser int
|
MaxRoundsWithoutUser int
|
||||||
|
TaskHistoryRetentionDays int
|
||||||
AllowedTaskKeywords []string
|
AllowedTaskKeywords []string
|
||||||
ImportantKeywords []string
|
ImportantKeywords []string
|
||||||
CompletionTemplate string
|
CompletionTemplate string
|
||||||
@@ -73,6 +74,7 @@ type Engine struct {
|
|||||||
lockOwners map[string]string
|
lockOwners map[string]string
|
||||||
roundsWithoutUser int
|
roundsWithoutUser int
|
||||||
lastDailyReportDate string
|
lastDailyReportDate string
|
||||||
|
lastHistoryCleanupAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine {
|
func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine {
|
||||||
@@ -106,6 +108,9 @@ func NewEngine(opts Options, msgBus *bus.MessageBus) *Engine {
|
|||||||
if opts.MaxRoundsWithoutUser < 0 {
|
if opts.MaxRoundsWithoutUser < 0 {
|
||||||
opts.MaxRoundsWithoutUser = 0
|
opts.MaxRoundsWithoutUser = 0
|
||||||
}
|
}
|
||||||
|
if opts.TaskHistoryRetentionDays <= 0 {
|
||||||
|
opts.TaskHistoryRetentionDays = 3
|
||||||
|
}
|
||||||
return &Engine{
|
return &Engine{
|
||||||
opts: opts,
|
opts: opts,
|
||||||
bus: msgBus,
|
bus: msgBus,
|
||||||
@@ -363,6 +368,7 @@ func (e *Engine) tick() {
|
|||||||
}
|
}
|
||||||
e.persistStateLocked()
|
e.persistStateLocked()
|
||||||
e.maybeWriteDailyReportLocked(now)
|
e.maybeWriteDailyReportLocked(now)
|
||||||
|
e.maybeCleanupTaskHistoryLocked(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) tryAcquireLocksLocked(st *taskState) bool {
|
func (e *Engine) tryAcquireLocksLocked(st *taskState) bool {
|
||||||
@@ -919,6 +925,68 @@ func appendUniqueReport(path, content, date string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Engine) maybeCleanupTaskHistoryLocked(now time.Time) {
|
||||||
|
if e.opts.TaskHistoryRetentionDays <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !e.lastHistoryCleanupAt.IsZero() && now.Sub(e.lastHistoryCleanupAt) < time.Hour {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
workspace := e.opts.Workspace
|
||||||
|
if workspace == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cutoff := now.AddDate(0, 0, -e.opts.TaskHistoryRetentionDays)
|
||||||
|
|
||||||
|
// Cleanup task-audit.jsonl by event time
|
||||||
|
auditPath := filepath.Join(workspace, "memory", "task-audit.jsonl")
|
||||||
|
if b, err := os.ReadFile(auditPath); err == nil {
|
||||||
|
lines := strings.Split(string(b), "\n")
|
||||||
|
kept := make([]string, 0, len(lines))
|
||||||
|
for _, ln := range lines {
|
||||||
|
if ln == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var row map[string]interface{}
|
||||||
|
if json.Unmarshal([]byte(ln), &row) != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ts := fmt.Sprintf("%v", row["time"])
|
||||||
|
tm, err := time.Parse(time.RFC3339, ts)
|
||||||
|
if err != nil || tm.After(cutoff) {
|
||||||
|
kept = append(kept, ln)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = os.WriteFile(auditPath, []byte(strings.Join(kept, "\n")+"\n"), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup tasks.json old terminal states
|
||||||
|
tasksPath := filepath.Join(workspace, "memory", "tasks.json")
|
||||||
|
if b, err := os.ReadFile(tasksPath); err == nil {
|
||||||
|
var items []TaskItem
|
||||||
|
if json.Unmarshal(b, &items) == nil {
|
||||||
|
kept := make([]TaskItem, 0, len(items))
|
||||||
|
for _, it := range items {
|
||||||
|
st := strings.ToLower(it.Status)
|
||||||
|
terminal := st == "done" || st == "completed" || st == "suppressed" || st == "error"
|
||||||
|
ts := strings.TrimSpace(it.UpdatedAt)
|
||||||
|
if !terminal || ts == "" {
|
||||||
|
kept = append(kept, it)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tm, err := time.Parse(time.RFC3339, ts)
|
||||||
|
if err != nil || tm.After(cutoff) {
|
||||||
|
kept = append(kept, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if out, err := json.MarshalIndent(kept, "", " "); err == nil {
|
||||||
|
_ = os.WriteFile(tasksPath, out, 0644)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.lastHistoryCleanupAt = now
|
||||||
|
}
|
||||||
|
|
||||||
func parseTodoAttributes(content string) (priority, dueAt, normalized string) {
|
func parseTodoAttributes(content string) (priority, dueAt, normalized string) {
|
||||||
priority = "normal"
|
priority = "normal"
|
||||||
normalized = content
|
normalized = content
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ type AutonomyConfig struct {
|
|||||||
QuietHours string `json:"quiet_hours" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_QUIET_HOURS"`
|
QuietHours string `json:"quiet_hours" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_QUIET_HOURS"`
|
||||||
UserIdleResumeSec int `json:"user_idle_resume_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_USER_IDLE_RESUME_SEC"`
|
UserIdleResumeSec int `json:"user_idle_resume_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_USER_IDLE_RESUME_SEC"`
|
||||||
MaxRoundsWithoutUser int `json:"max_rounds_without_user" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_ROUNDS_WITHOUT_USER"`
|
MaxRoundsWithoutUser int `json:"max_rounds_without_user" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_ROUNDS_WITHOUT_USER"`
|
||||||
|
TaskHistoryRetentionDays int `json:"task_history_retention_days" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_TASK_HISTORY_RETENTION_DAYS"`
|
||||||
WaitingResumeDebounceSec int `json:"waiting_resume_debounce_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_WAITING_RESUME_DEBOUNCE_SEC"`
|
WaitingResumeDebounceSec int `json:"waiting_resume_debounce_sec" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_WAITING_RESUME_DEBOUNCE_SEC"`
|
||||||
AllowedTaskKeywords []string `json:"allowed_task_keywords" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ALLOWED_TASK_KEYWORDS"`
|
AllowedTaskKeywords []string `json:"allowed_task_keywords" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_ALLOWED_TASK_KEYWORDS"`
|
||||||
NotifyChannel string `json:"notify_channel" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHANNEL"`
|
NotifyChannel string `json:"notify_channel" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHANNEL"`
|
||||||
@@ -327,6 +328,7 @@ func DefaultConfig() *Config {
|
|||||||
QuietHours: "23:00-08:00",
|
QuietHours: "23:00-08:00",
|
||||||
UserIdleResumeSec: 20,
|
UserIdleResumeSec: 20,
|
||||||
MaxRoundsWithoutUser: 12,
|
MaxRoundsWithoutUser: 12,
|
||||||
|
TaskHistoryRetentionDays: 3,
|
||||||
WaitingResumeDebounceSec: 5,
|
WaitingResumeDebounceSec: 5,
|
||||||
AllowedTaskKeywords: []string{},
|
AllowedTaskKeywords: []string{},
|
||||||
NotifyChannel: "",
|
NotifyChannel: "",
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ func Validate(cfg *Config) []error {
|
|||||||
if aut.WaitingResumeDebounceSec <= 0 {
|
if aut.WaitingResumeDebounceSec <= 0 {
|
||||||
errs = append(errs, fmt.Errorf("agents.defaults.autonomy.waiting_resume_debounce_sec must be > 0 when enabled=true"))
|
errs = append(errs, fmt.Errorf("agents.defaults.autonomy.waiting_resume_debounce_sec must be > 0 when enabled=true"))
|
||||||
}
|
}
|
||||||
|
if aut.TaskHistoryRetentionDays <= 0 {
|
||||||
|
errs = append(errs, fmt.Errorf("agents.defaults.autonomy.task_history_retention_days must be > 0 when enabled=true"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
texts := cfg.Agents.Defaults.Texts
|
texts := cfg.Agents.Defaults.Texts
|
||||||
if strings.TrimSpace(texts.NoResponseFallback) == "" {
|
if strings.TrimSpace(texts.NoResponseFallback) == "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user