diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index 87df993..0f939c1 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -205,7 +205,8 @@ func gatewayCmd() { reflect.DeepEqual(cfg.Tools, newCfg.Tools) && reflect.DeepEqual(cfg.Channels, newCfg.Channels) - changes := summarizeDialogTemplateChanges(cfg, newCfg) + templateChanges := summarizeDialogTemplateChanges(cfg, newCfg) + autonomyChanges := summarizeAutonomyChanges(cfg, newCfg) if runtimeSame { configureLogging(newCfg) @@ -230,8 +231,11 @@ func gatewayCmd() { sentinelService.Start() } cfg = newCfg - if len(changes) > 0 { - fmt.Printf("↻ Dialog template changes: %s\n", strings.Join(changes, ", ")) + if len(templateChanges) > 0 { + fmt.Printf("↻ Dialog template changes: %s\n", strings.Join(templateChanges, ", ")) + } + if len(autonomyChanges) > 0 { + fmt.Printf("↻ Autonomy changes: %s\n", strings.Join(autonomyChanges, ", ")) } fmt.Println("✓ Config hot-reload applied (logging/metadata only)") continue @@ -275,8 +279,11 @@ func gatewayCmd() { continue } go agentLoop.Run(ctx) - if len(changes) > 0 { - fmt.Printf("↻ Dialog template changes: %s\n", strings.Join(changes, ", ")) + if len(templateChanges) > 0 { + fmt.Printf("↻ Dialog template changes: %s\n", strings.Join(templateChanges, ", ")) + } + if len(autonomyChanges) > 0 { + fmt.Printf("↻ Autonomy changes: %s\n", strings.Join(autonomyChanges, ", ")) } fmt.Println("✓ Config hot-reload applied") default: @@ -294,6 +301,34 @@ func gatewayCmd() { } } +func summarizeAutonomyChanges(oldCfg, newCfg *config.Config) []string { + if oldCfg == nil || newCfg == nil { + return nil + } + o := oldCfg.Agents.Defaults.Autonomy + n := newCfg.Agents.Defaults.Autonomy + changes := make([]string, 0) + if o.Enabled != n.Enabled { + changes = append(changes, "enabled") + } + if o.TickIntervalSec != n.TickIntervalSec { + changes = append(changes, "tick_interval_sec") + } + if o.MinRunIntervalSec != n.MinRunIntervalSec { + changes = append(changes, "min_run_interval_sec") + } + if o.UserIdleResumeSec != n.UserIdleResumeSec { + changes = append(changes, "user_idle_resume_sec") + } + if strings.TrimSpace(o.QuietHours) != strings.TrimSpace(n.QuietHours) { + changes = append(changes, "quiet_hours") + } + if o.NotifyCooldownSec != n.NotifyCooldownSec { + changes = append(changes, "notify_cooldown_sec") + } + return changes +} + func summarizeDialogTemplateChanges(oldCfg, newCfg *config.Config) []string { if oldCfg == nil || newCfg == nil { return nil diff --git a/cmd/clawgo/cmd_status.go b/cmd/clawgo/cmd_status.go index e339a08..2c2b3d3 100644 --- a/cmd/clawgo/cmd_status.go +++ b/cmd/clawgo/cmd_status.go @@ -130,7 +130,7 @@ func statusCmd() { } } if summary, nextRetry, dedupeHits, err := collectAutonomyTaskSummary(filepath.Join(workspace, "memory", "tasks.json")); err == nil { - fmt.Printf("Autonomy Tasks: todo=%d doing=%d blocked=%d done=%d dedupe_hits=%d\n", summary["todo"], summary["doing"], summary["blocked"], summary["done"], dedupeHits) + 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) if nextRetry != "" { fmt.Printf("Autonomy Next Retry: %s\n", nextRetry) } @@ -252,7 +252,7 @@ func collectAutonomyTaskSummary(path string) (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, "blocked": 0, "done": 0}, "", 0, nil + return map[string]int{"todo": 0, "doing": 0, "waiting": 0, "blocked": 0, "done": 0}, "", 0, nil } return nil, "", 0, err } @@ -264,7 +264,7 @@ func collectAutonomyTaskSummary(path string) (map[string]int, string, int, error if err := json.Unmarshal(data, &items); err != nil { return nil, "", 0, err } - summary := map[string]int{"todo": 0, "doing": 0, "blocked": 0, "done": 0} + summary := map[string]int{"todo": 0, "doing": 0, "waiting": 0, "blocked": 0, "done": 0} nextRetry := "" nextRetryAt := time.Time{} totalDedupe := 0 diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 35e4ef5..cc72519 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -123,6 +123,7 @@ func (e *Engine) tick() { for _, st := range e.state { if st.Status == "running" { st.Status = "waiting" + e.writeReflectLog("waiting", st, "paused due to active user conversation") } } e.persistStateLocked() @@ -204,6 +205,10 @@ func (e *Engine) tick() { if st.Status == "completed" { continue } + if st.Status == "waiting" { + st.Status = "idle" + e.writeReflectLog("resume", st, "user conversation idle, autonomy resumed") + } if st.Status == "blocked" { if !st.RetryAfter.IsZero() && now.Before(st.RetryAfter) { continue @@ -437,6 +442,8 @@ func (e *Engine) persistStateLocked() { switch st.Status { case "running": status = "doing" + case "waiting": + status = "waiting" case "blocked": status = "blocked" case "completed": diff --git a/pkg/autonomy/task_store.go b/pkg/autonomy/task_store.go index fe386eb..d87fdac 100644 --- a/pkg/autonomy/task_store.go +++ b/pkg/autonomy/task_store.go @@ -14,7 +14,7 @@ type TaskItem struct { Content string `json:"content"` Priority string `json:"priority"` DueAt string `json:"due_at,omitempty"` - Status string `json:"status"` // todo|doing|blocked|done + Status string `json:"status"` // todo|doing|waiting|blocked|done RetryAfter string `json:"retry_after,omitempty"` Source string `json:"source"` DedupeHits int `json:"dedupe_hits,omitempty"`