refactor(channels): remove dead command parser remnants

This commit is contained in:
mingmxren 2026-03-01 17:49:46 +08:00
parent 15a1e7afdf
commit 9c610bc037
11 changed files with 17 additions and 340 deletions

View file

@ -5,6 +5,8 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"testing"
"time"

View file

@ -48,10 +48,3 @@ type PlaceholderRecorder interface {
type CommandRegistrarCapable interface {
RegisterCommands(ctx context.Context, defs []commands.Definition) error
}
// CommandParserCapable is implemented by channels that expose a command
// dispatch entrypoint backed by shared command definitions/dispatcher.
// It is optional and intended for cross-channel command handling features.
type CommandParserCapable interface {
DispatchCommand(ctx context.Context, req commands.Request) commands.Result
}

View file

@ -11,16 +11,6 @@ type mockRegistrar struct{}
func (mockRegistrar) RegisterCommands(context.Context, []commands.Definition) error { return nil }
type mockParser struct{}
func (mockParser) DispatchCommand(context.Context, commands.Request) commands.Result {
return commands.Result{Matched: false}
}
func TestCommandRegistrarCapable_Compiles(t *testing.T) {
var _ CommandRegistrarCapable = mockRegistrar{}
}
func TestCommandParserCapable_Compiles(t *testing.T) {
var _ CommandParserCapable = mockParser{}
}

View file

@ -41,14 +41,12 @@ var (
type TelegramChannel struct {
*channels.BaseChannel
bot *telego.Bot
bh *telegohandler.BotHandler
commands TelegramCommander
dispatcher commands.Dispatching
config *config.Config
chatIDs map[string]int64
ctx context.Context
cancel context.CancelFunc
bot *telego.Bot
bh *telegohandler.BotHandler
config *config.Config
chatIDs map[string]int64
ctx context.Context
cancel context.CancelFunc
registerFunc func(context.Context, []commands.Definition) error
commandRegCancel context.CancelFunc
@ -94,8 +92,6 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
return &TelegramChannel{
BaseChannel: base,
commands: NewTelegramCommands(bot, cfg),
dispatcher: commands.NewDispatcher(commands.NewRegistry(commands.BuiltinDefinitions(cfg))),
bot: bot,
config: cfg,
chatIDs: make(map[string]int64),
@ -123,9 +119,6 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
c.bh = bh
bh.HandleMessage(func(ctx *th.Context, message telego.Message) error {
if c.dispatchCommand(ctx, message) {
return nil
}
return c.handleMessage(ctx, &message)
}, th.AnyMessage())

View file

@ -1,154 +0,0 @@
package telegram
import (
"context"
"fmt"
"strings"
"github.com/mymmrac/telego"
"github.com/sipeed/picoclaw/pkg/commands"
"github.com/sipeed/picoclaw/pkg/config"
)
type TelegramCommander interface {
Help(ctx context.Context, message telego.Message) error
Start(ctx context.Context, message telego.Message) error
Show(ctx context.Context, message telego.Message) error
List(ctx context.Context, message telego.Message) error
}
type cmd struct {
bot *telego.Bot
config *config.Config
}
func NewTelegramCommands(bot *telego.Bot, cfg *config.Config) TelegramCommander {
return &cmd{
bot: bot,
config: cfg,
}
}
func commandArgs(text string) string {
parts := strings.SplitN(text, " ", 2)
if len(parts) < 2 {
return ""
}
return strings.TrimSpace(parts[1])
}
func (c *cmd) Help(ctx context.Context, message telego.Message) error {
defs := commands.NewRegistry(commands.BuiltinDefinitions(c.config)).ForChannel("telegram")
msg := commands.FormatHelpMessage(defs)
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
ChatID: telego.ChatID{ID: message.Chat.ID},
Text: msg,
ReplyParameters: &telego.ReplyParameters{
MessageID: message.MessageID,
},
})
return err
}
func (c *cmd) Start(ctx context.Context, message telego.Message) error {
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
ChatID: telego.ChatID{ID: message.Chat.ID},
Text: "Hello! I am PicoClaw 🦞",
ReplyParameters: &telego.ReplyParameters{
MessageID: message.MessageID,
},
})
return err
}
func (c *cmd) Show(ctx context.Context, message telego.Message) error {
args := commandArgs(message.Text)
if args == "" {
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
ChatID: telego.ChatID{ID: message.Chat.ID},
Text: "Usage: /show [model|channel]",
ReplyParameters: &telego.ReplyParameters{
MessageID: message.MessageID,
},
})
return err
}
var response string
switch args {
case "model":
response = fmt.Sprintf("Current Model: %s (Provider: %s)",
c.config.Agents.Defaults.GetModelName(),
c.config.Agents.Defaults.Provider)
case "channel":
response = "Current Channel: telegram"
default:
response = fmt.Sprintf("Unknown parameter: %s. Try 'model' or 'channel'.", args)
}
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
ChatID: telego.ChatID{ID: message.Chat.ID},
Text: response,
ReplyParameters: &telego.ReplyParameters{
MessageID: message.MessageID,
},
})
return err
}
func (c *cmd) List(ctx context.Context, message telego.Message) error {
args := commandArgs(message.Text)
if args == "" {
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
ChatID: telego.ChatID{ID: message.Chat.ID},
Text: "Usage: /list [models|channels]",
ReplyParameters: &telego.ReplyParameters{
MessageID: message.MessageID,
},
})
return err
}
var response string
switch args {
case "models":
provider := c.config.Agents.Defaults.Provider
if provider == "" {
provider = "configured default"
}
response = fmt.Sprintf("Configured Model: %s\nProvider: %s\n\nTo change models, update config.json",
c.config.Agents.Defaults.GetModelName(), provider)
case "channels":
var enabled []string
if c.config.Channels.Telegram.Enabled {
enabled = append(enabled, "telegram")
}
if c.config.Channels.WhatsApp.Enabled {
enabled = append(enabled, "whatsapp")
}
if c.config.Channels.Feishu.Enabled {
enabled = append(enabled, "feishu")
}
if c.config.Channels.Discord.Enabled {
enabled = append(enabled, "discord")
}
if c.config.Channels.Slack.Enabled {
enabled = append(enabled, "slack")
}
response = fmt.Sprintf("Enabled Channels:\n- %s", strings.Join(enabled, "\n- "))
default:
response = fmt.Sprintf("Unknown parameter: %s. Try 'models' or 'channels'.", args)
}
_, err := c.bot.SendMessage(ctx, &telego.SendMessageParams{
ChatID: telego.ChatID{ID: message.Chat.ID},
Text: response,
ReplyParameters: &telego.ReplyParameters{
MessageID: message.MessageID,
},
})
return err
}

View file

@ -1,22 +0,0 @@
package telegram
import (
"context"
"github.com/mymmrac/telego"
"github.com/sipeed/picoclaw/pkg/commands"
)
func (c *TelegramChannel) DispatchCommand(ctx context.Context, req commands.Request) commands.Result {
if c.dispatcher == nil {
return commands.Result{Matched: false}
}
return c.dispatcher.Dispatch(ctx, req)
}
func (c *TelegramChannel) dispatchCommand(ctx context.Context, message telego.Message) bool {
// Generic slash commands are now executed in the agent-centric command path.
// Channel adapters must not consume them locally.
return false
}

View file

@ -9,34 +9,8 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/commands"
)
func TestDispatchCommand_DoesNotConsumeGenericCommandsLocally(t *testing.T) {
ch := &TelegramChannel{}
called := false
ch.dispatcher = commands.DispatchFunc(func(context.Context, commands.Request) commands.Result {
called = true
return commands.Result{Matched: true, Command: "noop"}
})
msg := telego.Message{
Text: "/help",
MessageID: 7,
Chat: telego.Chat{
ID: 123,
},
}
handled := ch.dispatchCommand(context.Background(), msg)
if handled {
t.Fatalf("handled=%v", handled)
}
if called {
t.Fatalf("handled=%v called=%v", handled, called)
}
}
func TestHandleMessage_DoesNotConsumeGenericCommandsLocally(t *testing.T) {
messageBus := bus.NewMessageBus()
ch := &TelegramChannel{

View file

@ -11,7 +11,6 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/commands"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
@ -20,14 +19,13 @@ import (
type WhatsAppChannel struct {
*channels.BaseChannel
conn *websocket.Conn
config config.WhatsAppConfig
url string
dispatcher commands.Dispatching
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
connected bool
conn *websocket.Conn
config config.WhatsAppConfig
url string
ctx context.Context
cancel context.CancelFunc
mu sync.Mutex
connected bool
}
func NewWhatsAppChannel(cfg config.WhatsAppConfig, bus *bus.MessageBus) (*WhatsAppChannel, error) {
@ -44,7 +42,6 @@ func NewWhatsAppChannel(cfg config.WhatsAppConfig, bus *bus.MessageBus) (*WhatsA
BaseChannel: base,
config: cfg,
url: cfg.BridgeURL,
dispatcher: commands.NewDispatcher(commands.NewRegistry(commands.BuiltinDefinitions(nil))),
connected: false,
}, nil
}
@ -251,25 +248,5 @@ func (c *WhatsAppChannel) handleIncomingMessage(msg map[string]any) {
return
}
if c.tryHandleCommand(c.ctx, content, chatID, senderID, messageID) {
return
}
c.HandleMessage(c.ctx, peer, messageID, senderID, chatID, content, mediaPaths, metadata, sender)
}
func (c *WhatsAppChannel) tryHandleCommand(
ctx context.Context,
text, chatID, senderID, messageID string,
) bool {
// Generic slash commands are now executed in the agent-centric command path.
// Channel adapters must not consume them locally.
return false
}
func (c *WhatsAppChannel) DispatchCommand(ctx context.Context, req commands.Request) commands.Result {
if c.dispatcher == nil {
return commands.Result{Matched: false}
}
return c.dispatcher.Dispatch(ctx, req)
}

View file

@ -7,37 +7,14 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/commands"
"github.com/sipeed/picoclaw/pkg/config"
)
func TestTryHandleCommand_DoesNotConsumeGenericCommandsLocally(t *testing.T) {
ch := &WhatsAppChannel{}
called := false
ch.dispatcher = commands.DispatchFunc(func(context.Context, commands.Request) commands.Result {
called = true
return commands.Result{Matched: true, Handled: true}
})
handled := ch.tryHandleCommand(context.Background(), "/help", "chat1", "user1", "mid1")
if handled {
t.Fatalf("handled=%v", handled)
}
if called {
t.Fatalf("handled=%v called=%v", handled, called)
}
}
func TestHandleIncomingMessage_DoesNotConsumeGenericCommandsLocally(t *testing.T) {
messageBus := bus.NewMessageBus()
called := false
ch := &WhatsAppChannel{
BaseChannel: channels.NewBaseChannel("whatsapp", config.WhatsAppConfig{}, messageBus, nil),
dispatcher: commands.DispatchFunc(func(context.Context, commands.Request) commands.Result {
called = true
return commands.Result{Matched: true, Handled: true}
}),
ctx: context.Background(),
ctx: context.Background(),
}
ch.handleIncomingMessage(map[string]any{
@ -48,10 +25,6 @@ func TestHandleIncomingMessage_DoesNotConsumeGenericCommandsLocally(t *testing.T
"content": "/help",
})
if called {
t.Fatal("expected generic command dispatch to be bypassed")
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

View file

@ -14,37 +14,14 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/commands"
"github.com/sipeed/picoclaw/pkg/config"
)
func TestTryHandleCommand_DoesNotConsumeGenericCommandsLocally(t *testing.T) {
ch := &WhatsAppNativeChannel{}
called := false
ch.dispatcher = commands.DispatchFunc(func(context.Context, commands.Request) commands.Result {
called = true
return commands.Result{Matched: true, Handled: true}
})
handled := ch.tryHandleCommand(context.Background(), "/help", "chat1", "user1", "mid1")
if handled {
t.Fatalf("handled=%v", handled)
}
if called {
t.Fatalf("handled=%v called=%v", handled, called)
}
}
func TestHandleIncoming_DoesNotConsumeGenericCommandsLocally(t *testing.T) {
messageBus := bus.NewMessageBus()
called := false
ch := &WhatsAppNativeChannel{
BaseChannel: channels.NewBaseChannel("whatsapp_native", config.WhatsAppConfig{}, messageBus, nil),
dispatcher: commands.DispatchFunc(func(context.Context, commands.Request) commands.Result {
called = true
return commands.Result{Matched: true, Handled: true}
}),
runCtx: context.Background(),
runCtx: context.Background(),
}
evt := &events.Message{
@ -63,10 +40,6 @@ func TestHandleIncoming_DoesNotConsumeGenericCommandsLocally(t *testing.T) {
ch.handleIncoming(evt)
if called {
t.Fatal("expected generic command dispatch to be bypassed")
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

View file

@ -30,7 +30,6 @@ import (
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/commands"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/identity"
"github.com/sipeed/picoclaw/pkg/logger"
@ -56,7 +55,6 @@ type WhatsAppNativeChannel struct {
mu sync.Mutex
runCtx context.Context
runCancel context.CancelFunc
dispatcher commands.Dispatching
reconnectMu sync.Mutex
reconnecting bool
stopping atomic.Bool // set once Stop begins; prevents new wg.Add calls
@ -78,7 +76,6 @@ func NewWhatsAppNativeChannel(
BaseChannel: base,
config: cfg,
storePath: storePath,
dispatcher: commands.NewDispatcher(commands.NewRegistry(commands.BuiltinDefinitions(nil))),
}
return c, nil
}
@ -390,9 +387,6 @@ func (c *WhatsAppNativeChannel) handleIncoming(evt *events.Message) {
if !c.IsAllowedSender(sender) {
return
}
if c.tryHandleCommand(c.runCtx, content, chatID, senderID, messageID) {
return
}
logger.DebugCF(
"whatsapp",
@ -402,22 +396,6 @@ func (c *WhatsAppNativeChannel) handleIncoming(evt *events.Message) {
c.HandleMessage(c.runCtx, peer, messageID, senderID, chatID, content, mediaPaths, metadata, sender)
}
func (c *WhatsAppNativeChannel) tryHandleCommand(
ctx context.Context,
text, chatID, senderID, messageID string,
) bool {
// Generic slash commands are now executed in the agent-centric command path.
// Channel adapters must not consume them locally.
return false
}
func (c *WhatsAppNativeChannel) DispatchCommand(ctx context.Context, req commands.Request) commands.Result {
if c.dispatcher == nil {
return commands.Result{Matched: false}
}
return c.dispatcher.Dispatch(ctx, req)
}
func (c *WhatsAppNativeChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
if !c.IsRunning() {
return channels.ErrNotRunning