Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
57c1d37
refactor(channels): add factory registry and export SetRunning on Bas…
alexhoshina Feb 20, 2026
383687d
refactor(channels): replace direct constructors with factory registry…
alexhoshina Feb 20, 2026
36eb68d
refactor(channels): add channel subpackages and update gateway imports
alexhoshina Feb 20, 2026
952ae91
refactor(channels): remove old channel files from parent package
alexhoshina Feb 20, 2026
420eadc
refactor(channels): remove redundant setRunning method from BaseChannel
alexhoshina Feb 20, 2026
b1cbaab
refactor(channels): replace bool with atomic.Bool for running state i…
alexhoshina Feb 20, 2026
00fd70e
fix: golangci-lint run --fix
alexhoshina Feb 21, 2026
153198e
refactor(bus,channels): promote peer and messageID from metadata to s…
alexhoshina Feb 22, 2026
b6161ae
refactor(channels): unify Start/Stop lifecycle and fix goroutine/cont…
alexhoshina Feb 22, 2026
7001983
refactor(channels): unify message splitting and add per-channel worke…
alexhoshina Feb 22, 2026
8116bcb
refactor(media): add MediaStore for unified media file lifecycle mana…
alexhoshina Feb 22, 2026
a32d985
refactor(channels): add per-channel rate limiting and send retry with…
alexhoshina Feb 22, 2026
24e2ed7
refactor(bus): fix deadlock and concurrency issues in MessageBus
alexhoshina Feb 22, 2026
cc92a62
refactor(channels): standardize Send error classification with sentin…
alexhoshina Feb 22, 2026
d1551dc
refactor(channels): consolidate HTTP servers into shared server manag…
alexhoshina Feb 22, 2026
4c7a5df
feat(channels): add MediaSender optional interface for outbound media
alexhoshina Feb 22, 2026
437657c
refactor(channels): remove channel-side voice transcription (Phase 12)
alexhoshina Feb 22, 2026
4c653c6
refactor(channels): standardize group chat trigger filtering (Phase 8)
alexhoshina Feb 22, 2026
90b4a64
feat(channels): add typing/placeholder automation and Pico Protocol c…
alexhoshina Feb 22, 2026
ced55e7
fix: resolve golangci-lint issues in channel system
alexhoshina Feb 22, 2026
f4b0f08
refactor(channels): move SplitMessage from pkg/utils to pkg/channels
alexhoshina Feb 22, 2026
db3c1e0
fix: address PR review feedback across channel system
alexhoshina Feb 22, 2026
3fb4469
feat(identity): add unified user identity with canonical platform:id …
alexhoshina Feb 22, 2026
cea0b95
refactor(loop): disable media cleanup to prevent premature file deletion
alexhoshina Feb 23, 2026
94f59fb
fix: address PR #662 review comments (bus drain, context timeouts, on…
alexhoshina Feb 23, 2026
692efb2
chore: apply PR #697 comment translations to refactored channel subpa…
alexhoshina Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/picoclaw/cmd_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func agentCmd() {
}

msgBus := bus.NewMessageBus()
defer msgBus.Close()
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)

// Print agent startup info (only for interactive mode)
Expand Down
81 changes: 32 additions & 49 deletions cmd/picoclaw/cmd_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,36 @@ package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"time"

"github.com/sipeed/picoclaw/pkg/agent"
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
_ "github.com/sipeed/picoclaw/pkg/channels/dingtalk"
_ "github.com/sipeed/picoclaw/pkg/channels/discord"
_ "github.com/sipeed/picoclaw/pkg/channels/feishu"
_ "github.com/sipeed/picoclaw/pkg/channels/line"
_ "github.com/sipeed/picoclaw/pkg/channels/maixcam"
_ "github.com/sipeed/picoclaw/pkg/channels/onebot"
_ "github.com/sipeed/picoclaw/pkg/channels/pico"
_ "github.com/sipeed/picoclaw/pkg/channels/qq"
_ "github.com/sipeed/picoclaw/pkg/channels/slack"
_ "github.com/sipeed/picoclaw/pkg/channels/telegram"
_ "github.com/sipeed/picoclaw/pkg/channels/wecom"
_ "github.com/sipeed/picoclaw/pkg/channels/whatsapp"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/cron"
"github.com/sipeed/picoclaw/pkg/devices"
"github.com/sipeed/picoclaw/pkg/health"
"github.com/sipeed/picoclaw/pkg/heartbeat"
"github.com/sipeed/picoclaw/pkg/logger"
"github.com/sipeed/picoclaw/pkg/media"
"github.com/sipeed/picoclaw/pkg/providers"
"github.com/sipeed/picoclaw/pkg/state"
"github.com/sipeed/picoclaw/pkg/tools"
"github.com/sipeed/picoclaw/pkg/voice"
)

func gatewayCmd() {
Expand Down Expand Up @@ -112,50 +122,18 @@ func gatewayCmd() {
return tools.SilentResult(response)
})

channelManager, err := channels.NewManager(cfg, msgBus)
// Create media store for file lifecycle management
mediaStore := media.NewFileMediaStore()

channelManager, err := channels.NewManager(cfg, msgBus, mediaStore)
if err != nil {
fmt.Printf("Error creating channel manager: %v\n", err)
os.Exit(1)
}

// Inject channel manager into agent loop for command handling
// Inject channel manager and media store into agent loop
agentLoop.SetChannelManager(channelManager)

var transcriber *voice.GroqTranscriber
groqAPIKey := cfg.Providers.Groq.APIKey
if groqAPIKey == "" {
for _, mc := range cfg.ModelList {
if strings.HasPrefix(mc.Model, "groq/") && mc.APIKey != "" {
groqAPIKey = mc.APIKey
break
}
}
}
if groqAPIKey != "" {
transcriber = voice.NewGroqTranscriber(groqAPIKey)
logger.InfoC("voice", "Groq voice transcription enabled")
}

if transcriber != nil {
if telegramChannel, ok := channelManager.GetChannel("telegram"); ok {
if tc, ok := telegramChannel.(*channels.TelegramChannel); ok {
tc.SetTranscriber(transcriber)
logger.InfoC("voice", "Groq transcription attached to Telegram channel")
}
}
if discordChannel, ok := channelManager.GetChannel("discord"); ok {
if dc, ok := discordChannel.(*channels.DiscordChannel); ok {
dc.SetTranscriber(transcriber)
logger.InfoC("voice", "Groq transcription attached to Discord channel")
}
}
if slackChannel, ok := channelManager.GetChannel("slack"); ok {
if sc, ok := slackChannel.(*channels.SlackChannel); ok {
sc.SetTranscriber(transcriber)
logger.InfoC("voice", "Groq transcription attached to Slack channel")
}
}
}
agentLoop.SetMediaStore(mediaStore)

enabledChannels := channelManager.GetEnabledChannels()
if len(enabledChannels) > 0 {
Expand Down Expand Up @@ -192,16 +170,15 @@ func gatewayCmd() {
fmt.Println("βœ“ Device event service started")
}

// Setup shared HTTP server with health endpoints and webhook handlers
healthServer := health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port)
addr := fmt.Sprintf("%s:%d", cfg.Gateway.Host, cfg.Gateway.Port)
channelManager.SetupHTTPServer(addr, healthServer)

if err := channelManager.StartAll(ctx); err != nil {
fmt.Printf("Error starting channels: %v\n", err)
}

healthServer := health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port)
go func() {
if err := healthServer.Start(); err != nil && err != http.ErrServerClosed {
logger.ErrorCF("health", "Health server error", map[string]any{"error": err.Error()})
}
}()
fmt.Printf("βœ“ Health endpoints available at http://%s:%d/health and /ready\n", cfg.Gateway.Host, cfg.Gateway.Port)

go agentLoop.Run(ctx)
Expand All @@ -212,12 +189,18 @@ func gatewayCmd() {

fmt.Println("\nShutting down...")
cancel()
healthServer.Stop(context.Background())
msgBus.Close()

// Use a fresh context with timeout for graceful shutdown,
// since the original ctx is already cancelled.
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 15*time.Second)
defer shutdownCancel()

channelManager.StopAll(shutdownCtx)
deviceService.Stop()
heartbeatService.Stop()
cronService.Stop()
agentLoop.Stop()
channelManager.StopAll(ctx)
fmt.Println("βœ“ Gateway stopped")
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/time v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
Expand Down
Loading