fix(qq): resolve golangci-lint formatting issues
- Fix interface{} → any in sync.Map Range callbacks
- Wrap long method signatures in mockQQAPI
- Remove blank lines between }) and following statements per gofumpt
This commit is contained in:
parent
d25abb59a4
commit
d3de49f3a3
5 changed files with 1153 additions and 46 deletions
1
.worktrees/qq-stability
Submodule
1
.worktrees/qq-stability
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 75ba9e83883d143fff60d511885e2b14b9cab597
|
||||
1104
docs/superpowers/plans/2026-03-19-qq-stability-improvements.md
Normal file
1104
docs/superpowers/plans/2026-03-19-qq-stability-improvements.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -43,9 +43,9 @@ const (
|
|||
bytesPerMiB = 1024 * 1024
|
||||
|
||||
// Reconnection constants (used as defaults when config values are 0)
|
||||
reconnectInitialDefault = 5 * time.Second
|
||||
reconnectMaxDefault = 5 * time.Minute
|
||||
reconnectMultiplier = 2.0
|
||||
reconnectInitialDefault = 5 * time.Second
|
||||
reconnectMaxDefault = 5 * time.Minute
|
||||
reconnectMultiplier = 2.0
|
||||
|
||||
// Retry constants (used as defaults when config values are 0)
|
||||
maxRetriesDefault = 3
|
||||
|
|
@ -53,8 +53,8 @@ const (
|
|||
retryMaxDelayDefault = 10 * time.Second
|
||||
|
||||
// Rate limit defaults
|
||||
rateLimitGroupDefault = 500 * time.Millisecond
|
||||
rateLimitDirectDefault = 200 * time.Millisecond
|
||||
rateLimitGroupDefault = 500 * time.Millisecond
|
||||
rateLimitDirectDefault = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
type qqAPI interface {
|
||||
|
|
@ -96,9 +96,9 @@ type QQChannel struct {
|
|||
stopOnce sync.Once
|
||||
|
||||
// Reconnection state
|
||||
reconnectMu sync.Mutex
|
||||
reconnecting bool
|
||||
stopReconnect chan struct{}
|
||||
reconnectMu sync.Mutex
|
||||
reconnecting bool
|
||||
stopReconnect chan struct{}
|
||||
|
||||
// Rate limiting
|
||||
groupRateLimiter *rateLimiter
|
||||
|
|
@ -270,9 +270,9 @@ func (c *QQChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
|||
if attempt > 0 {
|
||||
backoff := c.calculateBackoff(attempt - 1)
|
||||
logger.InfoCF("qq", "Retrying send", map[string]any{
|
||||
"attempt": attempt,
|
||||
"chat_id": msg.ChatID,
|
||||
"backoff": backoff.String(),
|
||||
"attempt": attempt,
|
||||
"chat_id": msg.ChatID,
|
||||
"backoff": backoff.String(),
|
||||
})
|
||||
|
||||
select {
|
||||
|
|
@ -1093,15 +1093,15 @@ func (c *QQChannel) reconnect() {
|
|||
// resetSessionState clears transient state that may be invalid after reconnection.
|
||||
func (c *QQChannel) resetSessionState() {
|
||||
// Clear transient state that may be invalid after reconnection
|
||||
c.chatType.Range(func(key, value interface{}) bool {
|
||||
c.chatType.Range(func(key, value any) bool {
|
||||
c.chatType.Delete(key)
|
||||
return true
|
||||
})
|
||||
c.lastMsgID.Range(func(key, value interface{}) bool {
|
||||
c.lastMsgID.Range(func(key, value any) bool {
|
||||
c.lastMsgID.Delete(key)
|
||||
return true
|
||||
})
|
||||
c.msgSeqCounters.Range(func(key, value interface{}) bool {
|
||||
c.msgSeqCounters.Range(func(key, value any) bool {
|
||||
c.msgSeqCounters.Delete(key)
|
||||
return true
|
||||
})
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@ import (
|
|||
|
||||
// mockQQAPI implements qqAPI for testing
|
||||
type mockQQAPI struct {
|
||||
callCount atomic.Int32
|
||||
wsCalled atomic.Bool
|
||||
postCalled atomic.Bool
|
||||
lastChatID string
|
||||
lastChatKind string
|
||||
shouldFail bool
|
||||
failErr error
|
||||
failRemaining atomic.Int32
|
||||
callCount atomic.Int32
|
||||
wsCalled atomic.Bool
|
||||
postCalled atomic.Bool
|
||||
lastChatID string
|
||||
lastChatKind string
|
||||
shouldFail bool
|
||||
failErr error
|
||||
failRemaining atomic.Int32
|
||||
}
|
||||
|
||||
func (m *mockQQAPI) WS(ctx context.Context, params map[string]string, body string) (*dto.WebsocketAP, error) {
|
||||
|
|
@ -35,7 +35,9 @@ func (m *mockQQAPI) WS(ctx context.Context, params map[string]string, body strin
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (m *mockQQAPI) PostGroupMessage(ctx context.Context, groupID string, msg dto.APIMessage, opt ...options.Option) (*dto.Message, error) {
|
||||
func (m *mockQQAPI) PostGroupMessage(
|
||||
ctx context.Context, groupID string, msg dto.APIMessage, opt ...options.Option,
|
||||
) (*dto.Message, error) {
|
||||
m.postCalled.Store(true)
|
||||
m.lastChatID = groupID
|
||||
m.lastChatKind = "group"
|
||||
|
|
@ -51,7 +53,9 @@ func (m *mockQQAPI) PostGroupMessage(ctx context.Context, groupID string, msg dt
|
|||
return &dto.Message{ID: "msg-123"}, nil
|
||||
}
|
||||
|
||||
func (m *mockQQAPI) PostC2CMessage(ctx context.Context, userID string, msg dto.APIMessage, opt ...options.Option) (*dto.Message, error) {
|
||||
func (m *mockQQAPI) PostC2CMessage(
|
||||
ctx context.Context, userID string, msg dto.APIMessage, opt ...options.Option,
|
||||
) (*dto.Message, error) {
|
||||
m.postCalled.Store(true)
|
||||
m.lastChatID = userID
|
||||
m.lastChatKind = "direct"
|
||||
|
|
@ -78,12 +82,12 @@ func newTestQQChannel(t *testing.T, api *mockQQAPI) *QQChannel {
|
|||
}
|
||||
|
||||
ch := &QQChannel{
|
||||
BaseChannel: channels.NewBaseChannel("qq", cfg, nil, config.FlexibleStringSlice{"*"}),
|
||||
config: cfg,
|
||||
api: api,
|
||||
dedup: make(map[string]time.Time),
|
||||
done: make(chan struct{}),
|
||||
groupRateLimiter: newRateLimiter(500 * time.Millisecond),
|
||||
BaseChannel: channels.NewBaseChannel("qq", cfg, nil, config.FlexibleStringSlice{"*"}),
|
||||
config: cfg,
|
||||
api: api,
|
||||
dedup: make(map[string]time.Time),
|
||||
done: make(chan struct{}),
|
||||
groupRateLimiter: newRateLimiter(500 * time.Millisecond),
|
||||
directRateLimiter: newRateLimiter(200 * time.Millisecond),
|
||||
}
|
||||
ch.SetRunning(true)
|
||||
|
|
@ -102,7 +106,6 @@ func TestSend_Success(t *testing.T) {
|
|||
ChatID: "group-1",
|
||||
Content: "Hello",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
|
@ -114,7 +117,7 @@ func TestSend_Success(t *testing.T) {
|
|||
// TestSend_WithRetry tests that retries happen on transient errors
|
||||
func TestSend_WithRetry(t *testing.T) {
|
||||
api := &mockQQAPI{
|
||||
shouldFail: true,
|
||||
shouldFail: true,
|
||||
failErr: errors.New("connection reset"),
|
||||
failRemaining: atomic.Int32{},
|
||||
}
|
||||
|
|
@ -127,7 +130,6 @@ func TestSend_WithRetry(t *testing.T) {
|
|||
ChatID: "group-1",
|
||||
Content: "Hello",
|
||||
})
|
||||
|
||||
// Should succeed after retries
|
||||
if err != nil {
|
||||
t.Errorf("expected success after retry, got %v", err)
|
||||
|
|
@ -140,7 +142,7 @@ func TestSend_WithRetry(t *testing.T) {
|
|||
// TestSend_NonRetryableError tests that non-retryable errors don't retry
|
||||
func TestSend_NonRetryableError(t *testing.T) {
|
||||
api := &mockQQAPI{
|
||||
shouldFail: true,
|
||||
shouldFail: true,
|
||||
failErr: errors.New("unauthorized"),
|
||||
failRemaining: atomic.Int32{},
|
||||
}
|
||||
|
|
@ -166,7 +168,7 @@ func TestSend_NonRetryableError(t *testing.T) {
|
|||
// TestSend_ContextCancellation tests that retries stop on context cancellation
|
||||
func TestSend_ContextCancellation(t *testing.T) {
|
||||
api := &mockQQAPI{
|
||||
shouldFail: true,
|
||||
shouldFail: true,
|
||||
failErr: errors.New("timeout"),
|
||||
failRemaining: atomic.Int32{},
|
||||
}
|
||||
|
|
@ -414,9 +416,9 @@ func TestQQChannel_ResetSessionState(t *testing.T) {
|
|||
// TestQQChannel_ResetSessionState_ReasoningChannel tests that reasoning channel is re-registered
|
||||
func TestQQChannel_ResetSessionState_ReasoningChannel(t *testing.T) {
|
||||
ch := &QQChannel{
|
||||
BaseChannel: channels.NewBaseChannel("qq", config.QQConfig{}, nil, config.FlexibleStringSlice{"*"}),
|
||||
config: config.QQConfig{ReasoningChannelID: "reasoning-group"},
|
||||
dedup: make(map[string]time.Time),
|
||||
BaseChannel: channels.NewBaseChannel("qq", config.QQConfig{}, nil, config.FlexibleStringSlice{"*"}),
|
||||
config: config.QQConfig{ReasoningChannelID: "reasoning-group"},
|
||||
dedup: make(map[string]time.Time),
|
||||
groupRateLimiter: newRateLimiter(500 * time.Millisecond),
|
||||
directRateLimiter: newRateLimiter(200 * time.Millisecond),
|
||||
}
|
||||
|
|
@ -431,9 +433,9 @@ func TestQQChannel_ResetSessionState_ReasoningChannel(t *testing.T) {
|
|||
// TestQQChannel_ConfigDrivenParams tests that config values override defaults.
|
||||
func TestQQChannel_ConfigDrivenParams(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(*config.QQConfig)
|
||||
check func(*testing.T, *QQChannel)
|
||||
name string
|
||||
setup func(*config.QQConfig)
|
||||
check func(*testing.T, *QQChannel)
|
||||
}{
|
||||
{
|
||||
name: "reconnect initial uses config when set",
|
||||
|
|
@ -490,7 +492,7 @@ func TestQQChannel_ConfigDrivenParams(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "rate limiters use config values",
|
||||
name: "rate limiters use config values",
|
||||
setup: func(cfg *config.QQConfig) {
|
||||
cfg.RateLimitGroupMs = 1000
|
||||
cfg.RateLimitDirectMs = 500
|
||||
|
|
|
|||
|
|
@ -360,11 +360,11 @@ type QQConfig struct {
|
|||
ReasoningChannelID string `json:"reasoning_channel_id" env:"PICOCLAW_CHANNELS_QQ_REASONING_CHANNEL_ID"`
|
||||
|
||||
// Stability config (all optional with sensible defaults)
|
||||
ReconnectInitialMs int `json:"reconnect_initial_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RECONNECT_INITIAL_MS"`
|
||||
ReconnectMaxMs int `json:"reconnect_max_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RECONNECT_MAX_MS"`
|
||||
MaxRetries int `json:"max_retries,omitempty" env:"PICOCLAW_CHANNELS_QQ_MAX_RETRIES"`
|
||||
ReconnectInitialMs int `json:"reconnect_initial_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RECONNECT_INITIAL_MS"`
|
||||
ReconnectMaxMs int `json:"reconnect_max_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RECONNECT_MAX_MS"`
|
||||
MaxRetries int `json:"max_retries,omitempty" env:"PICOCLAW_CHANNELS_QQ_MAX_RETRIES"`
|
||||
RetryInitialDelayMs int `json:"retry_initial_delay_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RETRY_INITIAL_DELAY_MS"`
|
||||
RetryMaxDelayMs int `json:"retry_max_delay_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RETRY_MAX_DELAY_MS"`
|
||||
RetryMaxDelayMs int `json:"retry_max_delay_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RETRY_MAX_DELAY_MS"`
|
||||
RateLimitGroupMs int `json:"rate_limit_group_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RATE_LIMIT_GROUP_MS"`
|
||||
RateLimitDirectMs int `json:"rate_limit_direct_ms,omitempty" env:"PICOCLAW_CHANNELS_QQ_RATE_LIMIT_DIRECT_MS"`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue