mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
421 lines
10 KiB
Go
421 lines
10 KiB
Go
package agent
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/bus"
|
|
"github.com/sipeed/picoclaw/pkg/config"
|
|
)
|
|
|
|
func TestAgentRegistry_ListAgentsBuildsStructuredDescriptors(t *testing.T) {
|
|
mainWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
name: Main Frontmatter Name
|
|
description: Structured main agent
|
|
---
|
|
# Agent
|
|
|
|
Handle general requests.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, mainWorkspace)
|
|
|
|
supportWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
name: Support Frontmatter Name
|
|
description: Support frontmatter description
|
|
---
|
|
# Agent
|
|
|
|
Handle support tickets carefully.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, supportWorkspace)
|
|
|
|
cfg := testCfg([]config.AgentConfig{
|
|
{ID: "main", Default: true, Name: "Configured Main", Workspace: mainWorkspace},
|
|
{ID: "support", Workspace: supportWorkspace},
|
|
})
|
|
|
|
registry := NewAgentRegistry(cfg, &mockRegistryProvider{})
|
|
|
|
descriptors := registry.ListAgents(mainWorkspace)
|
|
if len(descriptors) != 2 {
|
|
t.Fatalf("expected 2 descriptors, got %d", len(descriptors))
|
|
}
|
|
|
|
if descriptors[0].ID != "main" {
|
|
t.Fatalf("expected current workspace agent first, got %q", descriptors[0].ID)
|
|
}
|
|
if descriptors[0].Name != "Main Frontmatter Name" {
|
|
t.Fatalf("expected frontmatter name to drive discovery, got %q", descriptors[0].Name)
|
|
}
|
|
if descriptors[0].Description != "Structured main agent" {
|
|
t.Fatalf("expected frontmatter description, got %q", descriptors[0].Description)
|
|
}
|
|
|
|
support, ok := registry.GetAgentDescriptor("support")
|
|
if !ok || support == nil {
|
|
t.Fatal("expected support descriptor lookup to succeed")
|
|
}
|
|
if support.Name != "Support Frontmatter Name" {
|
|
t.Fatalf("expected support frontmatter name, got %q", support.Name)
|
|
}
|
|
if support.Description != "Support frontmatter description" {
|
|
t.Fatalf("expected support frontmatter description, got %q", support.Description)
|
|
}
|
|
}
|
|
|
|
func TestAgentRegistry_ListSpawnableAgentsRespectsPermissions(t *testing.T) {
|
|
cfg := testCfg([]config.AgentConfig{
|
|
{
|
|
ID: "parent",
|
|
Default: true,
|
|
Subagents: &config.SubagentsConfig{
|
|
AllowAgents: []string{"child2", "child1"},
|
|
},
|
|
},
|
|
{ID: "child1"},
|
|
{ID: "child2"},
|
|
{ID: "restricted"},
|
|
})
|
|
cfg.Tools.Spawn.Enabled = true
|
|
cfg.Tools.Subagent.Enabled = true
|
|
|
|
al := NewAgentLoop(cfg, bus.NewMessageBus(), &mockRegistryProvider{})
|
|
defer al.Close()
|
|
|
|
descriptors := al.GetRegistry().ListSpawnableAgents("parent")
|
|
if len(descriptors) != 2 {
|
|
t.Fatalf("expected 2 spawnable descriptors, got %d: %+v", len(descriptors), descriptors)
|
|
}
|
|
if descriptors[0].ID != "child1" || descriptors[1].ID != "child2" {
|
|
t.Fatalf("expected sorted spawnable peers only, got %+v", descriptors)
|
|
}
|
|
}
|
|
|
|
func TestAgentRegistry_ListSpawnableAgentsRequiresSpawnTool(t *testing.T) {
|
|
cfg := testCfg([]config.AgentConfig{
|
|
{
|
|
ID: "parent",
|
|
Default: true,
|
|
Subagents: &config.SubagentsConfig{
|
|
AllowAgents: []string{"child"},
|
|
},
|
|
},
|
|
{ID: "child"},
|
|
})
|
|
cfg.Tools.Subagent.Enabled = true
|
|
|
|
al := NewAgentLoop(cfg, bus.NewMessageBus(), &mockRegistryProvider{})
|
|
defer al.Close()
|
|
|
|
if descriptors := al.GetRegistry().ListSpawnableAgents("parent"); len(descriptors) != 0 {
|
|
t.Fatalf("expected no spawnable descriptors without spawn tool, got %+v", descriptors)
|
|
}
|
|
}
|
|
|
|
func TestContextBuilder_BuildMessagesIncludesAgentDiscoverySection(t *testing.T) {
|
|
mainWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
description: Main agent
|
|
---
|
|
# Agent
|
|
|
|
Generalist.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, mainWorkspace)
|
|
|
|
researchWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
name: Research Agent
|
|
description: Research specialist
|
|
---
|
|
# Agent
|
|
|
|
Investigate deeply.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, researchWorkspace)
|
|
|
|
restrictedWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
name: Restricted Agent
|
|
description: Restricted specialist
|
|
---
|
|
# Agent
|
|
|
|
Handle restricted work.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, restrictedWorkspace)
|
|
|
|
cfg := testCfg([]config.AgentConfig{
|
|
{
|
|
ID: "main",
|
|
Default: true,
|
|
Workspace: mainWorkspace,
|
|
Subagents: &config.SubagentsConfig{
|
|
AllowAgents: []string{"research"},
|
|
},
|
|
},
|
|
{ID: "research", Workspace: researchWorkspace},
|
|
{ID: "restricted", Workspace: restrictedWorkspace},
|
|
})
|
|
cfg.Tools.ReadFile.Enabled = true
|
|
cfg.Tools.WriteFile.Enabled = true
|
|
cfg.Tools.Spawn.Enabled = true
|
|
cfg.Tools.Subagent.Enabled = true
|
|
|
|
al := NewAgentLoop(cfg, bus.NewMessageBus(), &mockRegistryProvider{})
|
|
defer al.Close()
|
|
|
|
mainAgent, ok := al.GetRegistry().GetAgent("main")
|
|
if !ok || mainAgent == nil {
|
|
t.Fatal("expected main agent")
|
|
}
|
|
|
|
messages := mainAgent.ContextBuilder.BuildMessages(
|
|
nil,
|
|
"",
|
|
"delegate wisely",
|
|
nil,
|
|
"telegram",
|
|
"chat-1",
|
|
"",
|
|
"",
|
|
)
|
|
if len(messages) == 0 {
|
|
t.Fatal("expected messages")
|
|
}
|
|
|
|
systemPrompt := messages[0].Content
|
|
if !strings.Contains(systemPrompt, "# Agent Discovery") {
|
|
t.Fatalf("expected discovery section in system prompt, got %q", systemPrompt)
|
|
}
|
|
if strings.Contains(systemPrompt, `"id": "main"`) {
|
|
t.Fatalf("did not expect self descriptor in discovery section, got %q", systemPrompt)
|
|
}
|
|
if !strings.Contains(systemPrompt, `"id": "research"`) ||
|
|
!strings.Contains(systemPrompt, `"description": "Research specialist"`) {
|
|
t.Fatalf("expected allowed peer descriptor in discovery section, got %q", systemPrompt)
|
|
}
|
|
if strings.Contains(systemPrompt, `"id": "restricted"`) ||
|
|
strings.Contains(systemPrompt, `"description": "Restricted specialist"`) {
|
|
t.Fatalf("did not expect restricted peer descriptor in discovery section, got %q", systemPrompt)
|
|
}
|
|
for _, forbidden := range []string{`"current_agent_id"`, `"available_tools"`, `"model"`, `"channels"`, `"skills"`, `"mcpServers"`, `"tools"`} {
|
|
if strings.Contains(systemPrompt, forbidden) {
|
|
t.Fatalf("did not expect %s in discovery section, got %q", forbidden, systemPrompt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContextBuilder_BuildMessagesOmitsAgentDiscoveryWithoutSpawnPermissions(t *testing.T) {
|
|
mainWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
description: Main agent
|
|
---
|
|
# Agent
|
|
|
|
Generalist.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, mainWorkspace)
|
|
|
|
researchWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
description: Research specialist
|
|
---
|
|
# Agent
|
|
|
|
Investigate deeply.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, researchWorkspace)
|
|
|
|
cfg := testCfg([]config.AgentConfig{
|
|
{ID: "main", Default: true, Workspace: mainWorkspace},
|
|
{ID: "research", Workspace: researchWorkspace},
|
|
})
|
|
cfg.Tools.ReadFile.Enabled = true
|
|
cfg.Tools.Spawn.Enabled = true
|
|
cfg.Tools.Subagent.Enabled = true
|
|
|
|
al := NewAgentLoop(cfg, bus.NewMessageBus(), &mockRegistryProvider{})
|
|
defer al.Close()
|
|
|
|
mainAgent, ok := al.GetRegistry().GetAgent("main")
|
|
if !ok || mainAgent == nil {
|
|
t.Fatal("expected main agent")
|
|
}
|
|
|
|
messages := mainAgent.ContextBuilder.BuildMessages(
|
|
nil,
|
|
"",
|
|
"handle locally",
|
|
nil,
|
|
"telegram",
|
|
"chat-1",
|
|
"",
|
|
"",
|
|
)
|
|
if len(messages) == 0 {
|
|
t.Fatal("expected messages")
|
|
}
|
|
|
|
systemPrompt := messages[0].Content
|
|
if strings.Contains(systemPrompt, "# Agent Discovery") {
|
|
t.Fatalf("did not expect discovery section without spawn permissions, got %q", systemPrompt)
|
|
}
|
|
if strings.Contains(systemPrompt, `"id": "research"`) {
|
|
t.Fatalf("did not expect unauthorized peer identity in system prompt, got %q", systemPrompt)
|
|
}
|
|
}
|
|
|
|
func TestContextBuilder_BuildMessagesOmitsAgentDiscoveryWithoutSpawnTool(t *testing.T) {
|
|
mainWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
description: Main agent
|
|
tools: [read_file]
|
|
---
|
|
# Agent
|
|
|
|
Generalist.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, mainWorkspace)
|
|
|
|
researchWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
description: Research specialist
|
|
---
|
|
# Agent
|
|
|
|
Investigate deeply.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, researchWorkspace)
|
|
|
|
cfg := testCfg([]config.AgentConfig{
|
|
{
|
|
ID: "main",
|
|
Default: true,
|
|
Workspace: mainWorkspace,
|
|
Subagents: &config.SubagentsConfig{
|
|
AllowAgents: []string{"research"},
|
|
},
|
|
},
|
|
{ID: "research", Workspace: researchWorkspace},
|
|
})
|
|
cfg.Tools.ReadFile.Enabled = true
|
|
cfg.Tools.Spawn.Enabled = true
|
|
cfg.Tools.Subagent.Enabled = true
|
|
|
|
al := NewAgentLoop(cfg, bus.NewMessageBus(), &mockRegistryProvider{})
|
|
defer al.Close()
|
|
|
|
mainAgent, ok := al.GetRegistry().GetAgent("main")
|
|
if !ok || mainAgent == nil {
|
|
t.Fatal("expected main agent")
|
|
}
|
|
|
|
messages := mainAgent.ContextBuilder.BuildMessages(
|
|
nil,
|
|
"",
|
|
"handle locally",
|
|
nil,
|
|
"telegram",
|
|
"chat-1",
|
|
"",
|
|
"",
|
|
)
|
|
if len(messages) == 0 {
|
|
t.Fatal("expected messages")
|
|
}
|
|
|
|
systemPrompt := messages[0].Content
|
|
if strings.Contains(systemPrompt, "# Agent Discovery") {
|
|
t.Fatalf("did not expect discovery section without spawn tool, got %q", systemPrompt)
|
|
}
|
|
if strings.Contains(systemPrompt, `"id": "research"`) {
|
|
t.Fatalf("did not expect peer identity without spawn tool, got %q", systemPrompt)
|
|
}
|
|
}
|
|
|
|
func TestContextBuilder_BuildMessagesOmitsAgentDiscoverySectionForSingleton(t *testing.T) {
|
|
mainWorkspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
description: Main agent
|
|
---
|
|
# Agent
|
|
|
|
Generalist.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, mainWorkspace)
|
|
|
|
cfg := testCfg([]config.AgentConfig{
|
|
{ID: "main", Default: true, Workspace: mainWorkspace},
|
|
})
|
|
cfg.Tools.ReadFile.Enabled = true
|
|
cfg.Tools.Spawn.Enabled = true
|
|
cfg.Tools.Subagent.Enabled = true
|
|
|
|
al := NewAgentLoop(cfg, bus.NewMessageBus(), &mockRegistryProvider{})
|
|
defer al.Close()
|
|
|
|
mainAgent, ok := al.GetRegistry().GetAgent("main")
|
|
if !ok || mainAgent == nil {
|
|
t.Fatal("expected main agent")
|
|
}
|
|
|
|
messages := mainAgent.ContextBuilder.BuildMessages(
|
|
nil,
|
|
"",
|
|
"handle locally",
|
|
nil,
|
|
"telegram",
|
|
"chat-1",
|
|
"",
|
|
"",
|
|
)
|
|
if len(messages) == 0 {
|
|
t.Fatal("expected messages")
|
|
}
|
|
|
|
systemPrompt := messages[0].Content
|
|
if strings.Contains(systemPrompt, "# Agent Discovery") {
|
|
t.Fatalf("did not expect discovery section for singleton registry, got %q", systemPrompt)
|
|
}
|
|
}
|
|
|
|
func TestAgentRegistry_ListAgentsFallsBackToFirstNonEmptyAgentLine(t *testing.T) {
|
|
workspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
name: Research Agent
|
|
---
|
|
|
|
|
|
First useful line.
|
|
Second line.
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, workspace)
|
|
|
|
cfg := testCfg([]config.AgentConfig{
|
|
{ID: "research", Default: true, Workspace: workspace},
|
|
})
|
|
|
|
registry := NewAgentRegistry(cfg, &mockRegistryProvider{})
|
|
descriptor, ok := registry.GetAgentDescriptor("research")
|
|
if !ok || descriptor == nil {
|
|
t.Fatal("expected research descriptor lookup to succeed")
|
|
}
|
|
if descriptor.Description != "First useful line." {
|
|
t.Fatalf("descriptor.Description = %q, want %q", descriptor.Description, "First useful line.")
|
|
}
|
|
}
|