From 791eb3b96c228d6e49037fca014b31c815ac5b3c Mon Sep 17 00:00:00 2001 From: DBT Date: Tue, 24 Feb 2026 04:27:50 +0000 Subject: [PATCH] make autonomy proactive messaging strategy configurable --- cmd/clawgo/cmd_gateway.go | 27 +++++++++++++++------------ config.example.json | 5 ++++- pkg/autonomy/engine.go | 28 ++++++++++++++++++++++------ pkg/config/config.go | 36 +++++++++++++++++++++--------------- pkg/config/validate.go | 9 +++++++++ 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index 16c43ef..23445ae 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -770,18 +770,21 @@ func buildHeartbeatService(cfg *config.Config, msgBus *bus.MessageBus) *heartbea func buildAutonomyEngine(cfg *config.Config, msgBus *bus.MessageBus) *autonomy.Engine { a := cfg.Agents.Defaults.Autonomy return autonomy.NewEngine(autonomy.Options{ - Enabled: a.Enabled, - TickIntervalSec: a.TickIntervalSec, - MinRunIntervalSec: a.MinRunIntervalSec, - MaxPendingDurationSec: a.MaxPendingDurationSec, - MaxConsecutiveStalls: a.MaxConsecutiveStalls, - MaxDispatchPerTick: a.MaxDispatchPerTick, - NotifyCooldownSec: a.NotifyCooldownSec, - QuietHours: a.QuietHours, - UserIdleResumeSec: a.UserIdleResumeSec, + Enabled: a.Enabled, + TickIntervalSec: a.TickIntervalSec, + MinRunIntervalSec: a.MinRunIntervalSec, + MaxPendingDurationSec: a.MaxPendingDurationSec, + MaxConsecutiveStalls: a.MaxConsecutiveStalls, + MaxDispatchPerTick: a.MaxDispatchPerTick, + NotifyCooldownSec: a.NotifyCooldownSec, + QuietHours: a.QuietHours, + UserIdleResumeSec: a.UserIdleResumeSec, WaitingResumeDebounceSec: a.WaitingResumeDebounceSec, - Workspace: cfg.WorkspacePath(), - DefaultNotifyChannel: a.NotifyChannel, - DefaultNotifyChatID: a.NotifyChatID, + ImportantKeywords: cfg.Agents.Defaults.Texts.AutonomyImportantKeywords, + CompletionTemplate: cfg.Agents.Defaults.Texts.AutonomyCompletionTemplate, + BlockedTemplate: cfg.Agents.Defaults.Texts.AutonomyBlockedTemplate, + Workspace: cfg.WorkspacePath(), + DefaultNotifyChannel: a.NotifyChannel, + DefaultNotifyChatID: a.NotifyChatID, }, msgBus) } diff --git a/config.example.json b/config.example.json index 87e8090..a6fdd56 100644 --- a/config.example.json +++ b/config.example.json @@ -39,7 +39,10 @@ "unsupported_action": "unsupported action", "system_rewrite_template": "Rewrite the following internal system update in concise user-facing language:\n\n%s", "runtime_compaction_note": "[runtime-compaction] removed %d old messages, kept %d recent messages", - "startup_compaction_note": "[startup-compaction] removed %d old messages, kept %d recent messages" + "startup_compaction_note": "[startup-compaction] removed %d old messages, kept %d recent messages", + "autonomy_important_keywords": ["urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"], + "autonomy_completion_template": "✅ 已完成:%s\n下一步:如需我继续处理后续,直接回复“继续 %s”", + "autonomy_blocked_template": "⚠️ 任务受阻:%s\n原因:%s\n建议:回复“继续 %s”我会按当前状态重试。" }, "context_compaction": { "enabled": true, diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 33ec5f4..306e669 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -33,6 +33,9 @@ type Options struct { QuietHours string UserIdleResumeSec int WaitingResumeDebounceSec int + ImportantKeywords []string + CompletionTemplate string + BlockedTemplate string } type taskState struct { @@ -385,16 +388,20 @@ func (e *Engine) dispatchTask(st *taskState) { func (e *Engine) sendCompletionNotification(st *taskState) { e.writeReflectLog("complete", st, "task marked completed") e.writeTriggerAudit("complete", st, "") - if !isHighValueCompletion(st) { + if !e.isHighValueCompletion(st) { return } if !e.shouldNotify("done:" + st.ID) { return } + tpl := strings.TrimSpace(e.opts.CompletionTemplate) + if tpl == "" { + tpl = "✅ 已完成:%s\n下一步:如需我继续处理后续,直接回复“继续 %s”" + } e.bus.PublishOutbound(bus.OutboundMessage{ Channel: e.opts.DefaultNotifyChannel, ChatID: e.opts.DefaultNotifyChatID, - Content: fmt.Sprintf("✅ 已完成:%s\n下一步:如需我继续处理后续,直接回复“继续 %s”", shortTask(st.Content), shortTask(st.Content)), + Content: fmt.Sprintf(tpl, shortTask(st.Content), shortTask(st.Content)), }) } @@ -404,10 +411,14 @@ func (e *Engine) sendFailureNotification(st *taskState, reason string) { if !e.shouldNotify("blocked:" + st.ID) { return } + tpl := strings.TrimSpace(e.opts.BlockedTemplate) + if tpl == "" { + tpl = "⚠️ 任务受阻:%s\n原因:%s\n建议:回复“继续 %s”我会按当前状态重试。" + } e.bus.PublishOutbound(bus.OutboundMessage{ Channel: e.opts.DefaultNotifyChannel, ChatID: e.opts.DefaultNotifyChatID, - Content: fmt.Sprintf("⚠️ 任务受阻:%s\n原因:%s\n建议:回复“继续 %s”我会按当前状态重试。", shortTask(st.Content), strings.TrimSpace(reason), shortTask(st.Content)), + Content: fmt.Sprintf(tpl, shortTask(st.Content), strings.TrimSpace(reason), shortTask(st.Content)), }) } @@ -754,7 +765,7 @@ func min(a, b int) int { return b } -func isHighValueCompletion(st *taskState) bool { +func (e *Engine) isHighValueCompletion(st *taskState) bool { if st == nil { return false } @@ -765,8 +776,13 @@ func isHighValueCompletion(st *taskState) bool { return true } s := strings.ToLower(strings.TrimSpace(st.Content)) - for _, k := range []string{"urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"} { - if strings.Contains(s, k) { + keywords := e.opts.ImportantKeywords + if len(keywords) == 0 { + keywords = []string{"urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"} + } + for _, k := range keywords { + kk := strings.ToLower(strings.TrimSpace(k)) + if kk != "" && strings.Contains(s, kk) { return true } } diff --git a/pkg/config/config.go b/pkg/config/config.go index 15b1a42..ac52846 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -60,18 +60,21 @@ type AutonomyConfig struct { } type AgentTextConfig struct { - NoResponseFallback string `json:"no_response_fallback"` - ThinkOnlyFallback string `json:"think_only_fallback"` - MemoryRecallKeywords []string `json:"memory_recall_keywords"` - LangUsage string `json:"lang_usage"` - LangInvalid string `json:"lang_invalid"` - LangUpdatedTemplate string `json:"lang_updated_template"` - SubagentsNone string `json:"subagents_none"` - SessionsNone string `json:"sessions_none"` - UnsupportedAction string `json:"unsupported_action"` - SystemRewriteTemplate string `json:"system_rewrite_template"` - RuntimeCompactionNote string `json:"runtime_compaction_note"` - StartupCompactionNote string `json:"startup_compaction_note"` + NoResponseFallback string `json:"no_response_fallback"` + ThinkOnlyFallback string `json:"think_only_fallback"` + MemoryRecallKeywords []string `json:"memory_recall_keywords"` + LangUsage string `json:"lang_usage"` + LangInvalid string `json:"lang_invalid"` + LangUpdatedTemplate string `json:"lang_updated_template"` + SubagentsNone string `json:"subagents_none"` + SessionsNone string `json:"sessions_none"` + UnsupportedAction string `json:"unsupported_action"` + SystemRewriteTemplate string `json:"system_rewrite_template"` + RuntimeCompactionNote string `json:"runtime_compaction_note"` + StartupCompactionNote string `json:"startup_compaction_note"` + AutonomyImportantKeywords []string `json:"autonomy_important_keywords"` + AutonomyCompletionTemplate string `json:"autonomy_completion_template"` + AutonomyBlockedTemplate string `json:"autonomy_blocked_template"` } type HeartbeatConfig struct { @@ -324,9 +327,12 @@ func DefaultConfig() *Config { SubagentsNone: "No subagents.", SessionsNone: "No sessions.", UnsupportedAction: "unsupported action", - SystemRewriteTemplate: "Rewrite the following internal system update in concise user-facing language:\n\n%s", - RuntimeCompactionNote: "[runtime-compaction] removed %d old messages, kept %d recent messages", - StartupCompactionNote: "[startup-compaction] removed %d old messages, kept %d recent messages", + SystemRewriteTemplate: "Rewrite the following internal system update in concise user-facing language:\n\n%s", + RuntimeCompactionNote: "[runtime-compaction] removed %d old messages, kept %d recent messages", + StartupCompactionNote: "[startup-compaction] removed %d old messages, kept %d recent messages", + AutonomyImportantKeywords: []string{"urgent", "重要", "付款", "payment", "上线", "release", "deadline", "截止"}, + AutonomyCompletionTemplate: "✅ 已完成:%s\n下一步:如需我继续处理后续,直接回复“继续 %s”", + AutonomyBlockedTemplate: "⚠️ 任务受阻:%s\n原因:%s\n建议:回复“继续 %s”我会按当前状态重试。", }, ContextCompaction: ContextCompactionConfig{ Enabled: true, diff --git a/pkg/config/validate.go b/pkg/config/validate.go index 1c574fc..3fb88df 100644 --- a/pkg/config/validate.go +++ b/pkg/config/validate.go @@ -138,6 +138,15 @@ func Validate(cfg *Config) []error { errs = append(errs, fmt.Errorf("agents.defaults.texts.startup_compaction_note must contain two %%d placeholders")) } } + if len(texts.AutonomyImportantKeywords) == 0 { + errs = append(errs, fmt.Errorf("agents.defaults.texts.autonomy_important_keywords must contain at least one keyword")) + } + if strings.Count(strings.TrimSpace(texts.AutonomyCompletionTemplate), "%s") < 2 { + errs = append(errs, fmt.Errorf("agents.defaults.texts.autonomy_completion_template must contain two %%s placeholders")) + } + if strings.Count(strings.TrimSpace(texts.AutonomyBlockedTemplate), "%s") < 3 { + errs = append(errs, fmt.Errorf("agents.defaults.texts.autonomy_blocked_template must contain three %%s placeholders")) + } if cfg.Agents.Defaults.ContextCompaction.Enabled { cc := cfg.Agents.Defaults.ContextCompaction