refactor(commands): address code review findings
- Remove dead ExecuteResult.Reply field and unused branch in loop.go - Extract shared agentsHandler for /show agents and /list agents - Remove redundant firstToken/secondToken (use nthToken instead) - Simplify Telegram startup: pass BuiltinDefinitions directly - Centralize req.Reply nil guard in executeDefinition - Extract unavailableMsg constant (was duplicated 5 times) - Remove unused MessageID from Request - Remove stale "reserved for Phase 2" comment on Deps.Config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ef861488ab
commit
c2fc9f3e2a
12 changed files with 47 additions and 96 deletions
|
|
@ -1533,9 +1533,6 @@ func (al *AgentLoop) handleCommand(
|
||||||
if commandReply != "" {
|
if commandReply != "" {
|
||||||
return commandReply, true
|
return commandReply, true
|
||||||
}
|
}
|
||||||
if result.Reply != "" {
|
|
||||||
return result.Reply, true
|
|
||||||
}
|
|
||||||
return "", true
|
return "", true
|
||||||
default:
|
default:
|
||||||
return "", false
|
return "", false
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
||||||
"username": c.bot.Username(),
|
"username": c.bot.Username(),
|
||||||
})
|
})
|
||||||
|
|
||||||
c.startCommandRegistration(c.ctx, commands.NewRegistry(commands.BuiltinDefinitions(&commands.Deps{Config: c.config})).Definitions())
|
c.startCommandRegistration(c.ctx, commands.BuiltinDefinitions(&commands.Deps{Config: c.config}))
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err = bh.Start(); err != nil {
|
if err = bh.Start(); err != nil {
|
||||||
|
|
|
||||||
21
pkg/commands/cmd_agents.go
Normal file
21
pkg/commands/cmd_agents.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// agentsHandler returns a shared handler for both /show agents and /list agents.
|
||||||
|
func agentsHandler(deps *Deps) Handler {
|
||||||
|
return func(_ context.Context, req Request) error {
|
||||||
|
if deps.ListAgentIDs == nil {
|
||||||
|
return req.Reply(unavailableMsg)
|
||||||
|
}
|
||||||
|
ids := deps.ListAgentIDs()
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return req.Reply("No agents registered")
|
||||||
|
}
|
||||||
|
return req.Reply(fmt.Sprintf("Registered agents: %s", strings.Join(ids, ", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,9 +12,6 @@ func helpCommand(deps *Deps) Definition {
|
||||||
Description: "Show this help message",
|
Description: "Show this help message",
|
||||||
Usage: "/help",
|
Usage: "/help",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: func(_ context.Context, req Request) error {
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defs := BuiltinDefinitions(deps)
|
defs := BuiltinDefinitions(deps)
|
||||||
return req.Reply(formatHelpMessage(defs))
|
return req.Reply(formatHelpMessage(defs))
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,8 @@ func listCommand(deps *Deps) Definition {
|
||||||
Name: "models",
|
Name: "models",
|
||||||
Description: "Configured models",
|
Description: "Configured models",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: func(_ context.Context, req Request) error {
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if deps.GetModelInfo == nil {
|
if deps.GetModelInfo == nil {
|
||||||
return req.Reply("Command unavailable in current context.")
|
return req.Reply(unavailableMsg)
|
||||||
}
|
}
|
||||||
name, provider := deps.GetModelInfo()
|
name, provider := deps.GetModelInfo()
|
||||||
if provider == "" {
|
if provider == "" {
|
||||||
|
|
@ -35,11 +32,8 @@ func listCommand(deps *Deps) Definition {
|
||||||
Name: "channels",
|
Name: "channels",
|
||||||
Description: "Enabled channels",
|
Description: "Enabled channels",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: func(_ context.Context, req Request) error {
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if deps.GetEnabledChannels == nil {
|
if deps.GetEnabledChannels == nil {
|
||||||
return req.Reply("Command unavailable in current context.")
|
return req.Reply(unavailableMsg)
|
||||||
}
|
}
|
||||||
enabled := deps.GetEnabledChannels()
|
enabled := deps.GetEnabledChannels()
|
||||||
if len(enabled) == 0 {
|
if len(enabled) == 0 {
|
||||||
|
|
@ -51,19 +45,7 @@ func listCommand(deps *Deps) Definition {
|
||||||
{
|
{
|
||||||
Name: "agents",
|
Name: "agents",
|
||||||
Description: "Registered agents",
|
Description: "Registered agents",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: agentsHandler(deps),
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if deps.ListAgentIDs == nil {
|
|
||||||
return req.Reply("Command unavailable in current context.")
|
|
||||||
}
|
|
||||||
ids := deps.ListAgentIDs()
|
|
||||||
if len(ids) == 0 {
|
|
||||||
return req.Reply("No agents registered")
|
|
||||||
}
|
|
||||||
return req.Reply(fmt.Sprintf("Registered agents: %s", strings.Join(ids, ", ")))
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package commands
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func showCommand(deps *Deps) Definition {
|
func showCommand(deps *Deps) Definition {
|
||||||
|
|
@ -15,11 +14,8 @@ func showCommand(deps *Deps) Definition {
|
||||||
Name: "model",
|
Name: "model",
|
||||||
Description: "Current model and provider",
|
Description: "Current model and provider",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: func(_ context.Context, req Request) error {
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if deps.GetModelInfo == nil {
|
if deps.GetModelInfo == nil {
|
||||||
return req.Reply("Command unavailable in current context.")
|
return req.Reply(unavailableMsg)
|
||||||
}
|
}
|
||||||
name, provider := deps.GetModelInfo()
|
name, provider := deps.GetModelInfo()
|
||||||
return req.Reply(fmt.Sprintf("Current Model: %s (Provider: %s)", name, provider))
|
return req.Reply(fmt.Sprintf("Current Model: %s (Provider: %s)", name, provider))
|
||||||
|
|
@ -29,28 +25,13 @@ func showCommand(deps *Deps) Definition {
|
||||||
Name: "channel",
|
Name: "channel",
|
||||||
Description: "Current channel",
|
Description: "Current channel",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: func(_ context.Context, req Request) error {
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return req.Reply(fmt.Sprintf("Current Channel: %s", req.Channel))
|
return req.Reply(fmt.Sprintf("Current Channel: %s", req.Channel))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "agents",
|
Name: "agents",
|
||||||
Description: "Registered agents",
|
Description: "Registered agents",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: agentsHandler(deps),
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if deps.ListAgentIDs == nil {
|
|
||||||
return req.Reply("Command unavailable in current context.")
|
|
||||||
}
|
|
||||||
ids := deps.ListAgentIDs()
|
|
||||||
if len(ids) == 0 {
|
|
||||||
return req.Reply("No agents registered")
|
|
||||||
}
|
|
||||||
return req.Reply(fmt.Sprintf("Registered agents: %s", strings.Join(ids, ", ")))
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ func startCommand() Definition {
|
||||||
Description: "Start the bot",
|
Description: "Start the bot",
|
||||||
Usage: "/start",
|
Usage: "/start",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: func(_ context.Context, req Request) error {
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return req.Reply("Hello! I am PicoClaw 🦞")
|
return req.Reply("Hello! I am PicoClaw 🦞")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,8 @@ func switchCommand(deps *Deps) Definition {
|
||||||
Description: "Switch to a different model",
|
Description: "Switch to a different model",
|
||||||
ArgsUsage: "to <name>",
|
ArgsUsage: "to <name>",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: func(_ context.Context, req Request) error {
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if deps.SwitchModel == nil {
|
if deps.SwitchModel == nil {
|
||||||
return req.Reply("Command unavailable in current context.")
|
return req.Reply(unavailableMsg)
|
||||||
}
|
}
|
||||||
// Parse: /switch model to <value>
|
// Parse: /switch model to <value>
|
||||||
value := nthToken(req.Text, 3) // tokens: [/switch, model, to, <value>]
|
value := nthToken(req.Text, 3) // tokens: [/switch, model, to, <value>]
|
||||||
|
|
@ -38,11 +35,8 @@ func switchCommand(deps *Deps) Definition {
|
||||||
Description: "Switch to a different channel",
|
Description: "Switch to a different channel",
|
||||||
ArgsUsage: "to <name>",
|
ArgsUsage: "to <name>",
|
||||||
Handler: func(_ context.Context, req Request) error {
|
Handler: func(_ context.Context, req Request) error {
|
||||||
if req.Reply == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if deps.SwitchChannel == nil {
|
if deps.SwitchChannel == nil {
|
||||||
return req.Reply("Command unavailable in current context.")
|
return req.Reply(unavailableMsg)
|
||||||
}
|
}
|
||||||
value := nthToken(req.Text, 3)
|
value := nthToken(req.Text, 3)
|
||||||
if nthToken(req.Text, 2) != "to" || value == "" {
|
if nthToken(req.Text, 2) != "to" || value == "" {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import "github.com/sipeed/picoclaw/pkg/config"
|
||||||
// invocation time, not at construction time, so late-bound values
|
// invocation time, not at construction time, so late-bound values
|
||||||
// (e.g. channelManager set after NewAgentLoop) are visible.
|
// (e.g. channelManager set after NewAgentLoop) are visible.
|
||||||
type Deps struct {
|
type Deps struct {
|
||||||
Config *config.Config // reserved for Phase 2 session commands
|
Config *config.Config
|
||||||
GetModelInfo func() (name, provider string)
|
GetModelInfo func() (name, provider string)
|
||||||
ListAgentIDs func() []string
|
ListAgentIDs func() []string
|
||||||
GetEnabledChannels func() []string
|
GetEnabledChannels func() []string
|
||||||
|
|
|
||||||
|
|
@ -8,28 +8,21 @@ import (
|
||||||
type Handler func(ctx context.Context, req Request) error
|
type Handler func(ctx context.Context, req Request) error
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Channel string
|
Channel string
|
||||||
ChatID string
|
ChatID string
|
||||||
SenderID string
|
SenderID string
|
||||||
Text string
|
Text string
|
||||||
MessageID string
|
Reply func(text string) error
|
||||||
Reply func(text string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unavailableMsg = "Command unavailable in current context."
|
||||||
|
|
||||||
var commandPrefixes = []string{"/", "!"}
|
var commandPrefixes = []string{"/", "!"}
|
||||||
|
|
||||||
func firstToken(input string) string {
|
|
||||||
parts := strings.Fields(strings.TrimSpace(input))
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return parts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseCommandName accepts "/name", "!name", and Telegram's "/name@bot", then
|
// parseCommandName accepts "/name", "!name", and Telegram's "/name@bot", then
|
||||||
// normalizes to lowercase command names.
|
// normalizes to lowercase command names.
|
||||||
func parseCommandName(input string) (string, bool) {
|
func parseCommandName(input string) (string, bool) {
|
||||||
token := firstToken(input)
|
token := nthToken(input, 0)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
@ -57,18 +50,10 @@ func trimCommandPrefix(token string) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func secondToken(input string) string {
|
|
||||||
parts := strings.Fields(strings.TrimSpace(input))
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return parts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasCommandPrefix returns true if the input starts with a recognized
|
// HasCommandPrefix returns true if the input starts with a recognized
|
||||||
// command prefix (e.g. "/" or "!").
|
// command prefix (e.g. "/" or "!").
|
||||||
func HasCommandPrefix(input string) bool {
|
func HasCommandPrefix(input string) bool {
|
||||||
token := firstToken(input)
|
token := nthToken(input, 0)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ const (
|
||||||
type ExecuteResult struct {
|
type ExecuteResult struct {
|
||||||
Outcome Outcome
|
Outcome Outcome
|
||||||
Command string
|
Command string
|
||||||
Reply string
|
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +50,11 @@ func (e *Executor) Execute(ctx context.Context, req Request) ExecuteResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Executor) executeDefinition(ctx context.Context, req Request, def Definition) ExecuteResult {
|
func (e *Executor) executeDefinition(ctx context.Context, req Request, def Definition) ExecuteResult {
|
||||||
|
// Ensure Reply is always non-nil so handlers don't need to check.
|
||||||
|
if req.Reply == nil {
|
||||||
|
req.Reply = func(string) error { return nil }
|
||||||
|
}
|
||||||
|
|
||||||
// Simple command — no sub-commands
|
// Simple command — no sub-commands
|
||||||
if len(def.SubCommands) == 0 {
|
if len(def.SubCommands) == 0 {
|
||||||
if def.Handler == nil {
|
if def.Handler == nil {
|
||||||
|
|
@ -61,11 +65,9 @@ func (e *Executor) executeDefinition(ctx context.Context, req Request, def Defin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub-command routing
|
// Sub-command routing
|
||||||
subName := secondToken(req.Text)
|
subName := nthToken(req.Text, 1)
|
||||||
if subName == "" {
|
if subName == "" {
|
||||||
if req.Reply != nil {
|
_ = req.Reply("Usage: " + def.EffectiveUsage())
|
||||||
_ = req.Reply("Usage: " + def.EffectiveUsage())
|
|
||||||
}
|
|
||||||
return ExecuteResult{Outcome: OutcomeHandled, Command: def.Name}
|
return ExecuteResult{Outcome: OutcomeHandled, Command: def.Name}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,8 +83,6 @@ func (e *Executor) executeDefinition(ctx context.Context, req Request, def Defin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown sub-command
|
// Unknown sub-command
|
||||||
if req.Reply != nil {
|
_ = req.Reply(fmt.Sprintf("Unknown parameter: %s. Usage: %s", subName, def.EffectiveUsage()))
|
||||||
_ = req.Reply(fmt.Sprintf("Unknown parameter: %s. Usage: %s", subName, def.EffectiveUsage()))
|
|
||||||
}
|
|
||||||
return ExecuteResult{Outcome: OutcomeHandled, Command: def.Name}
|
return ExecuteResult{Outcome: OutcomeHandled, Command: def.Name}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,9 +55,6 @@ func TestShowListHandlers_ChannelPolicy(t *testing.T) {
|
||||||
if passthrough.Command != "foo" {
|
if passthrough.Command != "foo" {
|
||||||
t.Fatalf("whatsapp /foo command=%q, want=%q", passthrough.Command, "foo")
|
t.Fatalf("whatsapp /foo command=%q, want=%q", passthrough.Command, "foo")
|
||||||
}
|
}
|
||||||
if passthrough.Reply != "" {
|
|
||||||
t.Fatalf("whatsapp /foo reply=%q, want empty", passthrough.Reply)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShowListHandlers_ListHandledOnAllChannels(t *testing.T) {
|
func TestShowListHandlers_ListHandledOnAllChannels(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue