From b2ec95364440c496edcf523785d7c8558a9cabb4 Mon Sep 17 00:00:00 2001 From: DBT Date: Tue, 24 Feb 2026 02:00:45 +0000 Subject: [PATCH] improve autonomy user-priority detection and add waiting/retry/priority telemetry --- cmd/clawgo/cmd_status.go | 21 +++++++++++----- pkg/autonomy/engine.go | 54 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/cmd/clawgo/cmd_status.go b/cmd/clawgo/cmd_status.go index 2c2b3d3..55d9e43 100644 --- a/cmd/clawgo/cmd_status.go +++ b/cmd/clawgo/cmd_status.go @@ -129,8 +129,9 @@ func statusCmd() { fmt.Printf(" - %s\n", key) } } - if summary, nextRetry, dedupeHits, err := collectAutonomyTaskSummary(filepath.Join(workspace, "memory", "tasks.json")); err == nil { + if summary, prio, nextRetry, dedupeHits, err := collectAutonomyTaskSummary(filepath.Join(workspace, "memory", "tasks.json")); err == nil { fmt.Printf("Autonomy Tasks: todo=%d doing=%d waiting=%d blocked=%d done=%d dedupe_hits=%d\n", summary["todo"], summary["doing"], summary["waiting"], summary["blocked"], summary["done"], dedupeHits) + fmt.Printf("Autonomy Priority: high=%d normal=%d low=%d\n", prio["high"], prio["normal"], prio["low"]) if nextRetry != "" { fmt.Printf("Autonomy Next Retry: %s\n", nextRetry) } @@ -248,23 +249,25 @@ func collectTriggerErrorCounts(path string) (map[string]int, error) { return counts, nil } -func collectAutonomyTaskSummary(path string) (map[string]int, string, int, error) { +func collectAutonomyTaskSummary(path string) (map[string]int, map[string]int, string, int, error) { data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { - return map[string]int{"todo": 0, "doing": 0, "waiting": 0, "blocked": 0, "done": 0}, "", 0, nil + return map[string]int{"todo": 0, "doing": 0, "waiting": 0, "blocked": 0, "done": 0}, map[string]int{"high": 0, "normal": 0, "low": 0}, "", 0, nil } - return nil, "", 0, err + return nil, nil, "", 0, err } var items []struct { Status string `json:"status"` + Priority string `json:"priority"` RetryAfter string `json:"retry_after"` DedupeHits int `json:"dedupe_hits"` } if err := json.Unmarshal(data, &items); err != nil { - return nil, "", 0, err + return nil, nil, "", 0, err } summary := map[string]int{"todo": 0, "doing": 0, "waiting": 0, "blocked": 0, "done": 0} + priorities := map[string]int{"high": 0, "normal": 0, "low": 0} nextRetry := "" nextRetryAt := time.Time{} totalDedupe := 0 @@ -274,6 +277,12 @@ func collectAutonomyTaskSummary(path string) (map[string]int, string, int, error summary[s]++ } totalDedupe += it.DedupeHits + p := strings.ToLower(strings.TrimSpace(it.Priority)) + if _, ok := priorities[p]; ok { + priorities[p]++ + } else { + priorities["normal"]++ + } if strings.TrimSpace(it.RetryAfter) != "" { if t, err := time.Parse(time.RFC3339, it.RetryAfter); err == nil { if nextRetryAt.IsZero() || t.Before(nextRetryAt) { @@ -283,7 +292,7 @@ func collectAutonomyTaskSummary(path string) (map[string]int, string, int, error } } } - return summary, nextRetry, totalDedupe, nil + return summary, priorities, nextRetry, totalDedupe, nil } func collectRecentSubagentSessions(sessionsDir string, limit int) ([]string, error) { diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index cc72519..cde480b 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -1,6 +1,7 @@ package autonomy import ( + "bufio" "context" "crypto/sha1" "encoding/hex" @@ -529,15 +530,14 @@ func (e *Engine) hasRecentUserActivity(now time.Time) bool { if e.opts.UserIdleResumeSec <= 0 || strings.TrimSpace(e.opts.Workspace) == "" { return false } - // sessions are stored next to workspace directory in clawgo runtime sessionsPath := filepath.Join(filepath.Dir(e.opts.Workspace), "sessions", "sessions.json") data, err := os.ReadFile(sessionsPath) if err != nil { return false } var index map[string]struct { - Kind string `json:"kind"` - UpdatedAt int64 `json:"updatedAt"` + Kind string `json:"kind"` + SessionFile string `json:"sessionFile"` } if err := json.Unmarshal(data, &index); err != nil { return false @@ -547,16 +547,60 @@ func (e *Engine) hasRecentUserActivity(now time.Time) bool { if strings.ToLower(strings.TrimSpace(row.Kind)) != "main" { continue } - if row.UpdatedAt <= 0 { + if strings.TrimSpace(row.SessionFile) == "" { continue } - if time.UnixMilli(row.UpdatedAt).After(cutoff) { + if ts := latestUserMessageTime(row.SessionFile); !ts.IsZero() && ts.After(cutoff) { return true } } return false } +func latestUserMessageTime(path string) time.Time { + f, err := os.Open(path) + if err != nil { + return time.Time{} + } + defer f.Close() + + var latest time.Time + s := bufio.NewScanner(f) + for s.Scan() { + line := s.Bytes() + + // OpenClaw-like event line + var ev struct { + Type string `json:"type"` + Timestamp string `json:"timestamp"` + Message *struct { + Role string `json:"role"` + } `json:"message"` + } + if err := json.Unmarshal(line, &ev); err == nil && ev.Message != nil { + if strings.ToLower(strings.TrimSpace(ev.Message.Role)) == "user" { + if t, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(ev.Timestamp)); err == nil && t.After(latest) { + latest = t + } else if t, err := time.Parse(time.RFC3339, strings.TrimSpace(ev.Timestamp)); err == nil && t.After(latest) { + latest = t + } + } + continue + } + + // Legacy line + var msg struct { + Role string `json:"role"` + } + if err := json.Unmarshal(line, &msg); err == nil { + if strings.ToLower(strings.TrimSpace(msg.Role)) == "user" { + latest = time.Now().UTC() + } + } + } + return latest +} + func blockedRetryBackoff(stalls int, minRunIntervalSec int) time.Duration { if minRunIntervalSec <= 0 { minRunIntervalSec = 20