feat(commands): centralized command registry with sub-command routing#959
feat(commands): centralized command registry with sub-command routing#959yinwm merged 24 commits intosipeed:mainfrom
Conversation
824d7a1 to
236ed47
Compare
236ed47 to
554aa40
Compare
351b3d1 to
ffc063e
Compare
mingmxren
left a comment
There was a problem hiding this comment.
Addressed remaining review comments.
… and registration
Documents the architecture decisions for fixing 5 Important issues from code review: SubCommand pattern, Deps struct, command-group files, Executor caching, and Telegram registration dedup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce SubCommand struct for declaring sub-commands structurally within a parent command Definition. The EffectiveUsage() method auto-generates usage strings from sub-command names and args, preventing drift between help text and actual handler behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ontains() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uses Registry.Lookup for O(1) command dispatch instead of iterating all definitions. Definitions with SubCommands are routed to matching sub-command handlers. Missing or unknown sub-commands reply with auto-generated usage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract show/list/start/help into individual cmd_*.go files. Replace config.Config parameter with Deps struct for runtime data. Restore /show agents and /list agents sub-commands. Use EffectiveUsage for auto-generated help text. Bridge external callers (agent/loop.go, telegram.go) with Deps wrapper until Task 5 fully wires the Deps fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…allbacks Create Executor once in NewAgentLoop instead of per-message. Deps closures capture AgentLoop pointer for late-bound access to channelManager and runtime agent model. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…andRegistration only Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…prefix Move /switch model and /switch channel handling from inline loop.go logic into cmd_switch.go using the SubCommand + Deps pattern. This removes the OutcomePassthrough branch in handleCommand entirely. Also replace the hardcoded "/" prefix check with commands.HasCommandPrefix so that "!" prefixed commands are correctly routed to the Executor. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
Separate stateless Registry (cached on AgentLoop) from per-request Runtime (passed to handlers at execution time). This enables future session management features to inject per-request context without modifying the command registry. - Rename Deps → Runtime, move to runtime.go - Change Handler signature: func(ctx, req) error → func(ctx, req, rt *Runtime) error - NewExecutor now takes (registry, runtime) — executor is created per-request - BuiltinDefinitions() no longer takes parameters (stateless) - AgentLoop caches cmdRegistry, builds Runtime via buildRuntime() - Update all cmd_*.go handlers and tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mplates The workspace/ directory contains both AGENT.md (legacy) and AGENTS.md (current). copyEmbeddedToTarget was copying both, causing the test TestCopyEmbeddedToTargetUsesAgentsMarkdown to fail. Skip AGENT.md during the walk to match the expected behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tness - Rename dispatcher.go → request.go (no Dispatcher type remains) - Rename cmd_agents.go → handler_agents.go (shared handler, not a top-level command) - Add modelMu to protect AgentInstance.Model writes in SwitchModel - Add ListDefinitions to Runtime so /help uses registry instead of BuiltinDefinitions() - Fix SwitchChannel message: validation-only callback should not say "Switched" - Propagate Reply errors in executor instead of discarding with _ = - Add HasCommandPrefix unit test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move handleCommand() before the routeErr gate so global commands (/help, /show, /switch) remain available even when routing fails. Context-dependent commands that need a routed agent will report "unavailable" through their nil-Runtime guards. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1d47d23 to
92ed0c5
Compare
92ed0c5 to
c87533e
Compare
…mand diff - Remove modelMu: message processing is serial, no concurrent writes - Pass routed agent to handleCommand/buildCommandsRuntime instead of always using default agent - GetModelInfo/SwitchModel are nil when agent is nil (route failed), handlers reply "unavailable" - Restore GetMyCommands + slices.Equal check before SetMyCommands to avoid unnecessary Telegram API calls on restart Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SwitchModel should only update the routed agent's runtime Model field. Writing to cfg.Agents.Defaults.ModelName was a behavioral change that corrupts the default agent config when switching a non-default agent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`/switch channel` Semantic IssueNoticed that `/switch channel` only validates if a channel exists, but doesn't actually switch anything. This is a historical issue (not introduced by this PR). SuggestionThis is a historical issue, not introduced by this PR. Consider creating a follow-up issue to discuss:
|
/switch channel only validates availability, not actually switching. Rename to /check channel to match actual behavior. /switch channel now shows a redirect message pointing users to the new command. Addresses review feedback from yinwm on PR sipeed#959. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Description
Introduces a centralized
pkg/commandspackage that serves as the single source of truth for all slash command metadata, parsing, and dispatch. Replaces scattered inline command handling acrossloop.goand channel adapters with a unified architecture.Architecture
Key Design Decisions
handleCommandcall). This cleanly separates "what commands exist" from "what resources they need".NewExecutor(registry, runtime)is created per-request, binding the stateless Registry with the current Runtime. This enables future session management to inject per-request context (e.g. session scope) without modifying the registry.func(ctx, req, rt) error: Handlers receive Runtime as an explicit parameter instead of closing over deps. This makes dependencies visible, testable, and per-request.Definition.EffectiveUsage()auto-generates help text from sub-command names, preventing metadata/handler drift.req.Replynil check is done once inexecuteDefinition, not in every handler.handleCommand()runs before checkingrouteErr, so global commands (/help,/show,/switch) remain available even when routing fails. Context-dependent commands that need a routed agent report "unavailable" via their nil-Runtime guards.Changes
New package:
pkg/commandsdefinition.go—Definition,SubCommand,EffectiveUsage()auto-generationregistry.go— O(1) map-based command lookup with alias supportexecutor.go— Two-outcome dispatch (Handled/Passthrough) with sub-command routing and Reply error propagationrequest.go—Request/Handlertypes, shared parsing:HasCommandPrefix(),nthToken(),parseCommandName()runtime.go— Per-request runtime dependency struct (ListDefinitions,SwitchModel,SwitchChannel, etc.)cmd_*.go— Per-group command definitions (start, help, show, list, switch)handler_agents.go— SharedagentsHandlerused by both/show agentsand/list agentsbuiltin.go— Aggregates all built-in definitions (stateless, no parameters)Modified:
pkg/agent/loop.gocmdRegistryasAgentLoopfield (created once in struct init)buildCommandsRuntime()constructs per-requestRuntime; wiresListDefinitionsfrom registryhandleCommand()runs before route error check — global commands work even when routing failsmodelMumutex protectsAgentInstance.Modelwrites inSwitchModelcallbackModified:
pkg/channels/telegramstartCommandRegistrationwith exponential backoff replaces blockinginitBotCommandsBuiltinDefinitions()metadataNew:
pkg/channels/interfaces.goCommandRegistrarCapableinterface for platform menu registrationType of Change
AI Code Generation
Related Issue
/helpcommand references may be incomplete #579 [BUG] Telegram/helpcommand references may be incompleteTechnical Context
pkg/commands/definition.go,pkg/commands/registry.go,pkg/commands/executor.go,pkg/commands/runtime.goTest Environment
Checklist