feat: add /new and /status slash commands to agent loop
Fixes #572: /new saves current session and clears conversation history, giving users a fresh start without restarting the process. Fixes #573: /status shows model, agent ID, channel, message count, and max iterations for debugging and visibility.
This commit is contained in:
parent
56c441e760
commit
08bb45aeb6
2 changed files with 191 additions and 0 deletions
|
|
@ -1071,6 +1071,8 @@ func (al *AgentLoop) handleCommand(ctx context.Context, msg bus.InboundMessage)
|
||||||
case "/help":
|
case "/help":
|
||||||
return `Available commands:
|
return `Available commands:
|
||||||
/help Show this help message
|
/help Show this help message
|
||||||
|
/new Start a new conversation
|
||||||
|
/status Show current session info
|
||||||
/show model Show current model
|
/show model Show current model
|
||||||
/show channel Show current channel
|
/show channel Show current channel
|
||||||
/show agents Show registered agents
|
/show agents Show registered agents
|
||||||
|
|
@ -1139,6 +1141,63 @@ func (al *AgentLoop) handleCommand(ctx context.Context, msg bus.InboundMessage)
|
||||||
return fmt.Sprintf("Unknown list target: %s", args[0]), true
|
return fmt.Sprintf("Unknown list target: %s", args[0]), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "/new":
|
||||||
|
// Resolve the route to find the correct agent and session key
|
||||||
|
route := al.registry.ResolveRoute(routing.RouteInput{
|
||||||
|
Channel: msg.Channel,
|
||||||
|
AccountID: msg.Metadata["account_id"],
|
||||||
|
Peer: extractPeer(msg),
|
||||||
|
GuildID: msg.Metadata["guild_id"],
|
||||||
|
TeamID: msg.Metadata["team_id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
agent, ok := al.registry.GetAgent(route.AgentID)
|
||||||
|
if !ok {
|
||||||
|
agent = al.registry.GetDefaultAgent()
|
||||||
|
}
|
||||||
|
if agent == nil {
|
||||||
|
return "No agent configured", true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save current session before clearing
|
||||||
|
sessionKey := route.SessionKey
|
||||||
|
agent.Sessions.Save(sessionKey)
|
||||||
|
|
||||||
|
// Clear the in-memory conversation history and summary
|
||||||
|
agent.Sessions.TruncateHistory(sessionKey, 0)
|
||||||
|
agent.Sessions.SetSummary(sessionKey, "")
|
||||||
|
agent.Sessions.Save(sessionKey)
|
||||||
|
|
||||||
|
return "Started a new conversation. Previous session saved.", true
|
||||||
|
|
||||||
|
case "/status":
|
||||||
|
// Resolve the route to find the correct agent and session key
|
||||||
|
route := al.registry.ResolveRoute(routing.RouteInput{
|
||||||
|
Channel: msg.Channel,
|
||||||
|
AccountID: msg.Metadata["account_id"],
|
||||||
|
Peer: extractPeer(msg),
|
||||||
|
GuildID: msg.Metadata["guild_id"],
|
||||||
|
TeamID: msg.Metadata["team_id"],
|
||||||
|
})
|
||||||
|
|
||||||
|
agent, ok := al.registry.GetAgent(route.AgentID)
|
||||||
|
if !ok {
|
||||||
|
agent = al.registry.GetDefaultAgent()
|
||||||
|
}
|
||||||
|
if agent == nil {
|
||||||
|
return "No agent configured", true
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionKey := route.SessionKey
|
||||||
|
history := agent.Sessions.GetHistory(sessionKey)
|
||||||
|
|
||||||
|
return fmt.Sprintf(`Status:
|
||||||
|
Model: %s
|
||||||
|
Agent: %s
|
||||||
|
Channel: %s
|
||||||
|
Messages: %d in current session
|
||||||
|
Max iterations: %d`, agent.Model, agent.ID, msg.Channel, len(history), agent.MaxIterations), true
|
||||||
|
|
||||||
case "/switch":
|
case "/switch":
|
||||||
if len(args) < 3 || args[1] != "to" {
|
if len(args) < 3 || args[1] != "to" {
|
||||||
return "Usage: /switch [model|channel] to <name>", true
|
return "Usage: /switch [model|channel] to <name>", true
|
||||||
|
|
|
||||||
|
|
@ -789,3 +789,135 @@ func TestHandleCommand_NotACommand(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleCommand_New(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
cfg := &config.Config{
|
||||||
|
Agents: config.AgentsConfig{
|
||||||
|
Defaults: config.AgentDefaults{
|
||||||
|
Workspace: tmpDir,
|
||||||
|
Model: "test-model",
|
||||||
|
MaxTokens: 4096,
|
||||||
|
MaxToolIterations: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
msgBus := bus.NewMessageBus()
|
||||||
|
provider := &mockProvider{}
|
||||||
|
al := NewAgentLoop(cfg, msgBus, provider)
|
||||||
|
|
||||||
|
// Add some messages to the session so we can verify they get cleared
|
||||||
|
defaultAgent := al.registry.GetDefaultAgent()
|
||||||
|
if defaultAgent == nil {
|
||||||
|
t.Fatal("No default agent found")
|
||||||
|
}
|
||||||
|
sessionKey := "agent:main:main"
|
||||||
|
defaultAgent.Sessions.AddMessage(sessionKey, "user", "hello")
|
||||||
|
defaultAgent.Sessions.AddMessage(sessionKey, "assistant", "hi there")
|
||||||
|
|
||||||
|
history := defaultAgent.Sessions.GetHistory(sessionKey)
|
||||||
|
if len(history) != 2 {
|
||||||
|
t.Fatalf("Expected 2 messages before /new, got %d", len(history))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
msg := bus.InboundMessage{
|
||||||
|
Channel: "test",
|
||||||
|
SenderID: "user1",
|
||||||
|
ChatID: "chat1",
|
||||||
|
Content: "/new",
|
||||||
|
}
|
||||||
|
|
||||||
|
response, handled := al.handleCommand(ctx, msg)
|
||||||
|
if !handled {
|
||||||
|
t.Fatal("Expected /new to be handled")
|
||||||
|
}
|
||||||
|
if !strings.Contains(response, "Started a new conversation") {
|
||||||
|
t.Errorf("Expected confirmation message, got: %s", response)
|
||||||
|
}
|
||||||
|
if !strings.Contains(response, "Previous session saved") {
|
||||||
|
t.Errorf("Expected 'Previous session saved' in response, got: %s", response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify history was cleared
|
||||||
|
history = defaultAgent.Sessions.GetHistory(sessionKey)
|
||||||
|
if len(history) != 0 {
|
||||||
|
t.Errorf("Expected 0 messages after /new, got %d", len(history))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify summary was cleared
|
||||||
|
summary := defaultAgent.Sessions.GetSummary(sessionKey)
|
||||||
|
if summary != "" {
|
||||||
|
t.Errorf("Expected empty summary after /new, got: %s", summary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleCommand_Status(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp("", "agent-test-*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
cfg := &config.Config{
|
||||||
|
Agents: config.AgentsConfig{
|
||||||
|
Defaults: config.AgentDefaults{
|
||||||
|
Workspace: tmpDir,
|
||||||
|
Model: "claude-3-opus",
|
||||||
|
MaxTokens: 4096,
|
||||||
|
MaxToolIterations: 15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
msgBus := bus.NewMessageBus()
|
||||||
|
provider := &mockProvider{}
|
||||||
|
al := NewAgentLoop(cfg, msgBus, provider)
|
||||||
|
|
||||||
|
// Add some messages so the count is nonzero
|
||||||
|
defaultAgent := al.registry.GetDefaultAgent()
|
||||||
|
if defaultAgent == nil {
|
||||||
|
t.Fatal("No default agent found")
|
||||||
|
}
|
||||||
|
sessionKey := "agent:main:main"
|
||||||
|
defaultAgent.Sessions.AddMessage(sessionKey, "user", "hello")
|
||||||
|
defaultAgent.Sessions.AddMessage(sessionKey, "assistant", "hi")
|
||||||
|
defaultAgent.Sessions.AddMessage(sessionKey, "user", "how are you?")
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
msg := bus.InboundMessage{
|
||||||
|
Channel: "telegram",
|
||||||
|
SenderID: "user1",
|
||||||
|
ChatID: "chat1",
|
||||||
|
Content: "/status",
|
||||||
|
}
|
||||||
|
|
||||||
|
response, handled := al.handleCommand(ctx, msg)
|
||||||
|
if !handled {
|
||||||
|
t.Fatal("Expected /status to be handled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify key fields are present
|
||||||
|
expectedFields := []struct {
|
||||||
|
label string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"Model", "claude-3-opus"},
|
||||||
|
{"Agent", "main"},
|
||||||
|
{"Channel", "telegram"},
|
||||||
|
{"Messages", "3 in current session"},
|
||||||
|
{"Max iterations", "15"},
|
||||||
|
}
|
||||||
|
for _, f := range expectedFields {
|
||||||
|
expected := fmt.Sprintf("%s: %s", f.label, f.value)
|
||||||
|
if !strings.Contains(response, expected) {
|
||||||
|
t.Errorf("Expected status to contain %q, got:\n%s", expected, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue