From 6705a2b4e810ea9a792580dd0781d9bdda4ab8c9 Mon Sep 17 00:00:00 2001 From: DBT Date: Sat, 28 Feb 2026 13:01:54 +0000 Subject: [PATCH] autonomy notify: enforce notify allowlist from config and add guard test --- cmd/clawgo/cmd_gateway.go | 1 + config.example.json | 3 ++- pkg/autonomy/engine.go | 13 ++++++++++++ pkg/autonomy/engine_notify_allowlist_test.go | 21 ++++++++++++++++++++ pkg/config/config.go | 6 ++++-- 5 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 pkg/autonomy/engine_notify_allowlist_test.go diff --git a/cmd/clawgo/cmd_gateway.go b/cmd/clawgo/cmd_gateway.go index 9208979..00b49f4 100644 --- a/cmd/clawgo/cmd_gateway.go +++ b/cmd/clawgo/cmd_gateway.go @@ -958,5 +958,6 @@ func buildAutonomyEngine(cfg *config.Config, msgBus *bus.MessageBus) *autonomy.E Workspace: cfg.WorkspacePath(), DefaultNotifyChannel: a.NotifyChannel, DefaultNotifyChatID: a.NotifyChatID, + NotifyAllowChats: a.NotifyAllowChats, }, msgBus) } diff --git a/config.example.json b/config.example.json index baee046..69f6821 100644 --- a/config.example.json +++ b/config.example.json @@ -26,7 +26,8 @@ "user_idle_resume_sec": 20, "waiting_resume_debounce_sec": 5, "notify_channel": "", - "notify_chat_id": "" + "notify_chat_id": "", + "notify_allow_chats": [] }, "texts": { "no_response_fallback": "I've completed processing but have no response to give.", diff --git a/pkg/autonomy/engine.go b/pkg/autonomy/engine.go index 7768f1b..c39e072 100644 --- a/pkg/autonomy/engine.go +++ b/pkg/autonomy/engine.go @@ -29,6 +29,7 @@ type Options struct { Workspace string DefaultNotifyChannel string DefaultNotifyChatID string + NotifyAllowChats []string NotifyCooldownSec int NotifySameReasonCooldownSec int QuietHours string @@ -636,6 +637,18 @@ func (e *Engine) shouldNotify(key string, reason string) bool { if strings.TrimSpace(e.opts.DefaultNotifyChannel) == "" || strings.TrimSpace(e.opts.DefaultNotifyChatID) == "" { return false } + if len(e.opts.NotifyAllowChats) > 0 { + allowed := false + for _, c := range e.opts.NotifyAllowChats { + if c == e.opts.DefaultNotifyChatID { + allowed = true + break + } + } + if !allowed { + return false + } + } now := time.Now() if inQuietHours(now, e.opts.QuietHours) { return false diff --git a/pkg/autonomy/engine_notify_allowlist_test.go b/pkg/autonomy/engine_notify_allowlist_test.go new file mode 100644 index 0000000..35ea282 --- /dev/null +++ b/pkg/autonomy/engine_notify_allowlist_test.go @@ -0,0 +1,21 @@ +package autonomy + +import "testing" + +func TestShouldNotify_RespectsNotifyAllowChats(t *testing.T) { + e := &Engine{opts: Options{ + DefaultNotifyChannel: "telegram", + DefaultNotifyChatID: "chat-1", + NotifyAllowChats: []string{"chat-2", "chat-3"}, + NotifyCooldownSec: 1, + NotifySameReasonCooldownSec: 1, + }} + if e.shouldNotify("k1", "") { + t.Fatalf("expected notify to be blocked when chat not in allowlist") + } + + e.opts.NotifyAllowChats = []string{"chat-1"} + if !e.shouldNotify("k2", "") { + t.Fatalf("expected notify to pass when chat in allowlist") + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 71ddd04..4e320fe 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -58,8 +58,9 @@ type AutonomyConfig struct { MaxRoundsWithoutUser int `json:"max_rounds_without_user" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_MAX_ROUNDS_WITHOUT_USER"` 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"` - NotifyChannel string `json:"notify_channel" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHANNEL"` - NotifyChatID string `json:"notify_chat_id" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHAT_ID"` + NotifyChannel string `json:"notify_channel" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHANNEL"` + NotifyChatID string `json:"notify_chat_id" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_CHAT_ID"` + NotifyAllowChats []string `json:"notify_allow_chats" env:"CLAWGO_AGENTS_DEFAULTS_AUTONOMY_NOTIFY_ALLOW_CHATS"` } type AgentTextConfig struct { @@ -330,6 +331,7 @@ func DefaultConfig() *Config { AllowedTaskKeywords: []string{}, NotifyChannel: "", NotifyChatID: "", + NotifyAllowChats: []string{}, }, Texts: AgentTextConfig{ NoResponseFallback: "I've completed processing but have no response to give.",