From e9898be505b3dc5f39ca5fa5e10f72df9a97e77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=8A=E3=83=A0?= Date: Thu, 9 Jul 2026 18:25:37 +0800 Subject: [PATCH] fix(discord): honor disabled group history limit (#1406) Co-authored-by: Collective Developer --- internal/channels/discord/discord.go | 7 +-- .../channels/discord/discord_config_test.go | 47 +++++++++++++++++++ internal/channels/discord/factory.go | 11 ++++- internal/config/config_channels.go | 2 +- internal/config/config_load.go | 3 ++ internal/config/config_load_test.go | 28 +++++++++++ .../components/channels/channel-schemas.ts | 2 +- .../pages/channels/channel-schemas.test.ts | 11 +++++ ui/web/src/pages/channels/channel-schemas.ts | 2 +- 9 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 internal/channels/discord/discord_config_test.go diff --git a/internal/channels/discord/discord.go b/internal/channels/discord/discord.go index e90161f7..4f9fc01a 100644 --- a/internal/channels/discord/discord.go +++ b/internal/channels/discord/discord.go @@ -61,11 +61,6 @@ func New(cfg config.DiscordConfig, msgBus *bus.MessageBus, pairingSvc store.Pair requireMention = *cfg.RequireMention } - historyLimit := cfg.HistoryLimit - if historyLimit == 0 { - historyLimit = channels.DefaultGroupHistoryLimit - } - ch := &Channel{ BaseChannel: base, session: session, @@ -77,7 +72,7 @@ func New(cfg config.DiscordConfig, msgBus *bus.MessageBus, pairingSvc store.Pair ch.SetRequireMention(requireMention) ch.SetPairingService(pairingSvc) ch.SetGroupHistory(channels.MakeHistory(channels.TypeDiscord, pendingStore, base.TenantID())) - ch.SetHistoryLimit(historyLimit) + ch.SetHistoryLimit(cfg.HistoryLimit) return ch, nil } diff --git a/internal/channels/discord/discord_config_test.go b/internal/channels/discord/discord_config_test.go new file mode 100644 index 00000000..bb9ae32f --- /dev/null +++ b/internal/channels/discord/discord_config_test.go @@ -0,0 +1,47 @@ +package discord + +import ( + "encoding/json" + "testing" + + "github.com/nextlevelbuilder/goclaw/internal/channels" + "github.com/nextlevelbuilder/goclaw/internal/config" +) + +func TestNewPreservesExplicitZeroHistoryLimit(t *testing.T) { + ch, err := New(config.DiscordConfig{Token: "token", HistoryLimit: 0}, nil, nil, nil, nil, nil, nil) + if err != nil { + t.Fatalf("New returned error: %v", err) + } + if got := ch.HistoryLimit(); got != 0 { + t.Fatalf("HistoryLimit() = %d, want 0", got) + } +} + +func TestFactoryDefaultsMissingHistoryLimitButPreservesExplicitZero(t *testing.T) { + creds := json.RawMessage(`{"token":"token"}`) + + defaulted, err := Factory("discord-main", creds, json.RawMessage(`{}`), nil, nil) + if err != nil { + t.Fatalf("Factory default config returned error: %v", err) + } + defaultedDiscord, ok := defaulted.(*Channel) + if !ok { + t.Fatalf("Factory returned %T, want *discord.Channel", defaulted) + } + if got := defaultedDiscord.HistoryLimit(); got != channels.DefaultGroupHistoryLimit { + t.Fatalf("default HistoryLimit() = %d, want %d", got, channels.DefaultGroupHistoryLimit) + } + + disabled, err := Factory("discord-disabled", creds, json.RawMessage(`{"history_limit":0}`), nil, nil) + if err != nil { + t.Fatalf("Factory explicit zero returned error: %v", err) + } + disabledDiscord, ok := disabled.(*Channel) + if !ok { + t.Fatalf("Factory returned %T, want *discord.Channel", disabled) + } + if got := disabledDiscord.HistoryLimit(); got != 0 { + t.Fatalf("explicit zero HistoryLimit() = %d, want 0", got) + } +} diff --git a/internal/channels/discord/factory.go b/internal/channels/discord/factory.go index 52efcc13..3ca90ec4 100644 --- a/internal/channels/discord/factory.go +++ b/internal/channels/discord/factory.go @@ -22,7 +22,7 @@ type discordInstanceConfig struct { GroupPolicy string `json:"group_policy,omitempty"` AllowFrom []string `json:"allow_from,omitempty"` RequireMention *bool `json:"require_mention,omitempty"` - HistoryLimit int `json:"history_limit,omitempty"` + HistoryLimit *int `json:"history_limit,omitempty"` BlockReply *bool `json:"block_reply,omitempty"` ChatBehavior *config.ChatBehaviorConfig `json:"chat_behavior,omitempty"` MediaMaxBytes int64 `json:"media_max_bytes,omitempty"` @@ -81,7 +81,7 @@ func buildChannel(name string, creds json.RawMessage, cfg json.RawMessage, DMPolicy: ic.DMPolicy, GroupPolicy: ic.GroupPolicy, RequireMention: ic.RequireMention, - HistoryLimit: ic.HistoryLimit, + HistoryLimit: resolveHistoryLimit(ic.HistoryLimit), BlockReply: ic.BlockReply, ChatBehavior: ic.ChatBehavior, MediaMaxBytes: ic.MediaMaxBytes, @@ -105,3 +105,10 @@ func buildChannel(name string, creds json.RawMessage, cfg json.RawMessage, ch.SetName(name) return ch, nil } + +func resolveHistoryLimit(limit *int) int { + if limit == nil { + return channels.DefaultGroupHistoryLimit + } + return *limit +} diff --git a/internal/config/config_channels.go b/internal/config/config_channels.go index f63440d0..5c6c615e 100644 --- a/internal/config/config_channels.go +++ b/internal/config/config_channels.go @@ -152,7 +152,7 @@ type DiscordConfig struct { DMPolicy string `json:"dm_policy,omitempty"` // "open" (default), "allowlist", "disabled" GroupPolicy string `json:"group_policy,omitempty"` // "open" (default), "allowlist", "disabled" RequireMention *bool `json:"require_mention,omitempty"` // require @bot mention in groups (default true) - HistoryLimit int `json:"history_limit,omitempty"` // max pending group messages for context (default 50, 0=disabled) + HistoryLimit int `json:"history_limit,omitempty"` // max pending group messages for context (default 200, 0=disabled) BlockReply *bool `json:"block_reply,omitempty"` // override gateway block_reply (nil = inherit) ChatBehavior *ChatBehaviorConfig `json:"chat_behavior,omitempty"` // override gateway chat behavior (nil = inherit) MediaMaxBytes int64 `json:"media_max_bytes,omitempty"` // max media download size (default 25MB) diff --git a/internal/config/config_load.go b/internal/config/config_load.go index 07429b63..dcc5febf 100644 --- a/internal/config/config_load.go +++ b/internal/config/config_load.go @@ -97,6 +97,9 @@ func Default() *Config { Telegram: TelegramConfig{ ReactionLevel: "full", }, + Discord: DiscordConfig{ + HistoryLimit: 200, + }, }, Gateway: GatewayConfig{ Host: "0.0.0.0", diff --git a/internal/config/config_load_test.go b/internal/config/config_load_test.go index 57df6e61..592c176e 100644 --- a/internal/config/config_load_test.go +++ b/internal/config/config_load_test.go @@ -40,6 +40,9 @@ func TestDefault_SensibleDefaults(t *testing.T) { if cfg.Skills.SlashCommands.EffectivePrefix() != "/" { t.Fatalf("slash command prefix = %q, want /", cfg.Skills.SlashCommands.EffectivePrefix()) } + if cfg.Channels.Discord.HistoryLimit != 200 { + t.Fatalf("default discord history limit: got %d, want 200", cfg.Channels.Discord.HistoryLimit) + } } @@ -53,6 +56,9 @@ func TestLoad_MissingFile_UsesDefaults(t *testing.T) { if cfg.Gateway.Port != 18790 { t.Fatalf("expected default port, got %d", cfg.Gateway.Port) } + if cfg.Channels.Discord.HistoryLimit != 200 { + t.Fatalf("expected default discord history limit, got %d", cfg.Channels.Discord.HistoryLimit) + } } // --- Load with valid JSON5 --- @@ -87,6 +93,28 @@ func TestLoad_ValidJSON5(t *testing.T) { } } +func TestLoad_DiscordHistoryLimitExplicitZeroDisables(t *testing.T) { + dir := t.TempDir() + cfgPath := filepath.Join(dir, "config.json5") + + content := `{ + "channels": { + "discord": { + "history_limit": 0, + }, + }, + }` + os.WriteFile(cfgPath, []byte(content), 0644) + + cfg, err := Load(cfgPath) + if err != nil { + t.Fatalf("load error: %v", err) + } + if cfg.Channels.Discord.HistoryLimit != 0 { + t.Fatalf("discord history_limit explicit zero: got %d, want 0", cfg.Channels.Discord.HistoryLimit) + } +} + // --- Load with invalid JSON5 → error --- func TestLoad_InvalidJSON5(t *testing.T) { diff --git a/ui/desktop/frontend/src/components/channels/channel-schemas.ts b/ui/desktop/frontend/src/components/channels/channel-schemas.ts index cf96d5d1..075b7ce2 100644 --- a/ui/desktop/frontend/src/components/channels/channel-schemas.ts +++ b/ui/desktop/frontend/src/components/channels/channel-schemas.ts @@ -84,7 +84,7 @@ export const configSchema: Record = { { key: 'dm_policy', label: 'DM Policy', type: 'select', options: dmPolicyOptions, defaultValue: 'pairing' }, { key: 'group_policy', label: 'Group Policy', type: 'select', options: groupPolicyOptions, defaultValue: 'pairing' }, { key: 'require_mention', label: 'Require @mention in groups', type: 'boolean', defaultValue: true }, - { key: 'history_limit', label: 'Group History Limit', type: 'number', defaultValue: 50, help: 'Max pending group messages for context (0 = disabled)' }, + { key: 'history_limit', label: 'Group History Limit', type: 'number', defaultValue: 200, help: 'Max pending group messages for context (0 = disabled)' }, { key: 'allow_from', label: 'Allowed Users', type: 'tags', help: 'Discord user IDs' }, { key: 'block_reply', label: 'Block Reply', type: 'select', options: blockReplyOptions, defaultValue: 'inherit', help: 'Deliver intermediate text during tool iterations' }, ], diff --git a/ui/web/src/pages/channels/channel-schemas.test.ts b/ui/web/src/pages/channels/channel-schemas.test.ts index 3db7ea96..c1d5c96f 100644 --- a/ui/web/src/pages/channels/channel-schemas.test.ts +++ b/ui/web/src/pages/channels/channel-schemas.test.ts @@ -73,6 +73,17 @@ describe("telegram configSchema", () => { }); }); +describe("discord configSchema", () => { + const discordConfig = configSchema["discord"]!; + + it("matches the backend pending group history default", () => { + const historyLimit = discordConfig.find((field) => field.key === "history_limit"); + expect(historyLimit).toBeDefined(); + expect(historyLimit!.defaultValue).toBe(200); + expect(historyLimit!.help).toMatch(/0 = disabled/i); + }); +}); + describe("pancake configSchema", () => { const pancakeConfig = configSchema["pancake"]!; diff --git a/ui/web/src/pages/channels/channel-schemas.ts b/ui/web/src/pages/channels/channel-schemas.ts index e1d9ccf0..063f2169 100644 --- a/ui/web/src/pages/channels/channel-schemas.ts +++ b/ui/web/src/pages/channels/channel-schemas.ts @@ -173,7 +173,7 @@ export const configSchema: Record = { { key: "dm_policy", label: "DM Policy", type: "select", options: dmPolicyOptions, defaultValue: "pairing" }, { key: "group_policy", label: "Group Policy", type: "select", options: groupPolicyOptions, defaultValue: "pairing" }, { key: "require_mention", label: "Require @mention in groups", type: "boolean", defaultValue: true }, - { key: "history_limit", label: "Group History Limit", type: "number", defaultValue: 50, help: "Max pending group messages for context (0 = disabled)" }, + { key: "history_limit", label: "Group History Limit", type: "number", defaultValue: 200, help: "Max pending group messages for context (0 = disabled)" }, { key: "allow_from", label: "Allowed Users", type: "tags", help: "Discord user IDs" }, ...chatBehaviorOverrideFields, ],