Files
picoclaw/pkg/agent/agent_mcp_test.go
T
sky5454 329e68e017 refactor(agent): Agent Looper refactor phase2, restructure pipeline and rename loop files to agent (#2585)
* refactor(agent): introduce interfaces for MessageBus and ChannelManager

Phase 2 of loop.go refactor — dependency inversion using adapter pattern.

- Add interfaces.MessageBus and interfaces.ChannelManager interfaces
- Create adapters/messagebus.go wrapping *bus.MessageBus
- Create adapters/channelmanager.go wrapping *channels.Manager
- Update AgentLoop to use interfaces instead of concrete types
- Update registerSharedTools to accept interfaces.MessageBus

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(agent): restructure pipeline and rename loop files

Pipeline refactoring:
- Split pipeline.go (1400 lines) into focused files:
  - pipeline_setup.go (~115 lines): SetupTurn method
  - pipeline_llm.go (~519 lines): CallLLM method
  - pipeline_execute.go (~693 lines): ExecuteTools method
  - pipeline_finalize.go (~78 lines): Finalize method
- Pipeline struct and NewPipeline remain in pipeline.go (~39 lines)

Agent file renaming:
- Rename loop_*.go to agent_*.go for consistent naming:
  - loop.go -> agent.go, loop_message.go -> agent_message.go, etc.
- Merge turn.go + turn_exec.go into turn_state.go
- Rename loop_turn.go -> turn_coord.go

Documentation:
- Update docs/pipeline-restructuring-plan.md
- Add docs/agent-rename-plan.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(agent): code format  fixed

* refactor(agent): code test file added/renamed

* docs(agent): update agent refactor docs

* fix(agent): fix agent hardAbortX

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-21 10:55:50 +08:00

136 lines
3.5 KiB
Go

// PicoClaw - Ultra-lightweight personal AI agent
// Inspired by and based on nanobot: https://github.com/HKUDS/nanobot
// License: MIT
//
// Copyright (c) 2026 PicoClaw contributors
package agent
import (
"context"
"errors"
"testing"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/mcp"
)
func boolPtr(b bool) *bool { return &b }
func TestMCPRuntimeResetClearsState(t *testing.T) {
var rt mcpRuntime
manager := mcp.NewManager()
rt.setManager(manager)
rt.setInitErr(errors.New("stale init error"))
rt.initOnce.Do(func() {})
got := rt.reset()
if got != manager {
t.Fatalf("reset() manager = %p, want %p", got, manager)
}
if rt.hasManager() {
t.Fatal("expected manager to be cleared after reset")
}
if err := rt.getInitErr(); err != nil {
t.Fatalf("getInitErr() = %v, want nil", err)
}
reran := false
rt.initOnce.Do(func() { reran = true })
if !reran {
t.Fatal("expected initOnce to be reset")
}
}
func TestReloadProviderAndConfig_ResetsMCPRuntime(t *testing.T) {
al, cfg, _, _, cleanup := newTestAgentLoop(t)
defer cleanup()
defer al.Close()
manager := mcp.NewManager()
al.mcp.setManager(manager)
al.mcp.setInitErr(errors.New("stale init error"))
al.mcp.initOnce.Do(func() {})
if !al.mcp.hasManager() {
t.Fatal("expected MCP manager to exist before reload")
}
if err := al.ReloadProviderAndConfig(context.Background(), &mockProvider{}, cfg); err != nil {
t.Fatalf("ReloadProviderAndConfig() error = %v", err)
}
if al.mcp.hasManager() {
t.Fatal("expected MCP manager to be cleared when reloaded config has MCP disabled")
}
if err := al.mcp.getInitErr(); err != nil {
t.Fatalf("getInitErr() = %v, want nil", err)
}
reran := false
al.mcp.initOnce.Do(func() { reran = true })
if !reran {
t.Fatal("expected MCP initOnce to be reset after reload")
}
}
func TestServerIsDeferred(t *testing.T) {
tests := []struct {
name string
discoveryEnabled bool
serverDeferred *bool
want bool
}{
// --- global false always wins: per-server deferred is ignored ---
{
name: "global false: per-server deferred=true is ignored",
discoveryEnabled: false,
serverDeferred: boolPtr(true),
want: false,
},
{
name: "global false: per-server deferred=false stays false",
discoveryEnabled: false,
serverDeferred: boolPtr(false),
want: false,
},
// --- global true: per-server override applies ---
{
name: "global true: per-server deferred=false opts out",
discoveryEnabled: true,
serverDeferred: boolPtr(false),
want: false,
},
{
name: "global true: per-server deferred=true stays true",
discoveryEnabled: true,
serverDeferred: boolPtr(true),
want: true,
},
// --- no per-server override: fall back to global ---
{
name: "no per-server field, global discovery enabled",
discoveryEnabled: true,
serverDeferred: nil,
want: true,
},
{
name: "no per-server field, global discovery disabled",
discoveryEnabled: false,
serverDeferred: nil,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
serverCfg := config.MCPServerConfig{Deferred: tt.serverDeferred}
got := serverIsDeferred(tt.discoveryEnabled, serverCfg)
if got != tt.want {
t.Errorf("serverIsDeferred(discoveryEnabled=%v, deferred=%v) = %v, want %v",
tt.discoveryEnabled, tt.serverDeferred, got, tt.want)
}
})
}
}