mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
* created roadmap and yaml claude agent
* Update roadmap.md
* feat: Clean advanced analysis implementation - core agents, engine, artifacts
* Remove unrelated files - keep only advanced analysis implementation
* fix: Fix goroutine leak in hosted agent rate limiter
- Added stop channel and stopped flag to RateLimiter struct
- Modified replenishTokens to listen for stop signal and exit cleanly
- Added Stop() method to gracefully shutdown rate limiter
- Added Stop() method to HostedAgent to cleanup rate limiter on shutdown
Fixes cursor bot issue: Rate Limiter Goroutine Leak
* fix: Fix analyzer config and model validation bugs
Bug 1: Analyzer Config Missing File Path
- Added filePath to DeploymentStatus analyzer config in convertAnalyzerToSpec
- Sets namespace-specific path (cluster-resources/deployments/{namespace}.json)
- Falls back to generic path (cluster-resources/deployments.json) if no namespace
- Fixes LocalAgent.analyzeDeploymentStatus backward compatibility
Bug 2: HealthCheck Fails Model Validation
- Changed Ollama model validation from prefix match to exact match
- Prevents false positives where llama2:13b would match request for llama2:7b
- Ensures agent only reports healthy when exact model is available
Both fixes address cursor bot reported issues and maintain backward compatibility.
* fixing lint errors
* fixing lint errors
* adding CLI flags
* fix: resolve linting errors for CI
- Remove unnecessary nil check in host_kernel_configs.go (len() for nil slices is zero)
- Remove unnecessary fmt.Sprintf() calls in ceph.go for static strings
- Apply go fmt formatting fixes
Fixes failing lint CI check
* fix: resolve CI failures in build-test workflow and Ollama tests
1. Fix GitHub Actions workflow logic error:
- Replace problematic contains() expression with explicit job result checks
- Properly handle failure and cancelled states for each job
- Prevents false positive failures in success summary job
2. Fix Ollama agent parseLLMResponse panics:
- Add proper error handling for malformed JSON in LLM responses
- Return error when JSON is found but invalid (instead of silent fallback)
- Add error when no meaningful content can be parsed from response
- Prevents nil pointer dereference in test assertions
Fixes failing build-test/success and build-test/test CI checks
* fix: resolve all CI failures and cursor bot issues
1. Fix disable-ollama flag logic bug:
- Remove disable-ollama from advanced analysis trigger condition
- Prevents unintended advanced analysis mode when no agents registered
- Allows proper fallback to legacy analysis
2. Fix diff test consistency:
- Update test expectations to match function behavior (lines with newlines)
- Ensures consistency between streaming and non-streaming diff paths
3. Fix Ollama agent error handling:
- Add proper error return for malformed JSON in LLM responses
- Add meaningful content validation for markdown parsing
- Prevents nil pointer panics in test assertions
4. Fix analysis engine mock agent:
- Mock agent now processes and returns results for all provided analyzers
- Fixes test expectation mismatch (expected 8 results, got 1)
Resolves all failing CI checks: lint, test, and success workflow logic
---------
Co-authored-by: Noah Campbell <noah.edward.campbell@gmail.com>
253 lines
6.5 KiB
Go
253 lines
6.5 KiB
Go
package analyzer
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestPhase2_MultiAgentIntegration tests Phase 2 multi-agent coordination
|
|
func TestPhase2_MultiAgentIntegration(t *testing.T) {
|
|
ctx := context.Background()
|
|
engine := NewAnalysisEngine()
|
|
|
|
// Register multiple agents to test Phase 2 coordination
|
|
localAgent := &integrationTestAgent{
|
|
name: "local",
|
|
available: true,
|
|
results: []*AnalyzerResult{
|
|
{
|
|
IsPass: true,
|
|
Title: "Local Pod Check",
|
|
Message: "Local analysis passed",
|
|
AgentName: "local",
|
|
Confidence: 0.9,
|
|
},
|
|
},
|
|
}
|
|
|
|
hostedAgent := &integrationTestAgent{
|
|
name: "hosted",
|
|
available: true,
|
|
results: []*AnalyzerResult{
|
|
{
|
|
IsWarn: true,
|
|
Title: "Hosted AI Analysis",
|
|
Message: "AI detected potential issue",
|
|
AgentName: "hosted",
|
|
Confidence: 0.8,
|
|
},
|
|
},
|
|
}
|
|
|
|
ollamaAgent := &integrationTestAgent{
|
|
name: "ollama",
|
|
available: true,
|
|
results: []*AnalyzerResult{
|
|
{
|
|
IsFail: true,
|
|
Title: "Ollama Deep Analysis",
|
|
Message: "LLM found critical issue",
|
|
AgentName: "ollama",
|
|
Confidence: 0.85,
|
|
Remediation: &RemediationStep{
|
|
Description: "LLM-suggested remediation",
|
|
Priority: 9,
|
|
Category: "ai-suggested",
|
|
IsAutomatable: false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Register all agents
|
|
require.NoError(t, engine.RegisterAgent("local", localAgent))
|
|
require.NoError(t, engine.RegisterAgent("hosted", hostedAgent))
|
|
require.NoError(t, engine.RegisterAgent("ollama", ollamaAgent))
|
|
|
|
// Test multi-agent analysis
|
|
bundle := &SupportBundle{
|
|
Files: map[string][]byte{
|
|
"test.json": []byte(`{"test": "data"}`),
|
|
},
|
|
Metadata: &SupportBundleMetadata{
|
|
CreatedAt: time.Now(),
|
|
Version: "1.0.0",
|
|
},
|
|
}
|
|
|
|
// Test with multiple agents
|
|
opts := AnalysisOptions{
|
|
Agents: []string{"local", "hosted", "ollama"},
|
|
IncludeRemediation: true,
|
|
}
|
|
|
|
result, err := engine.Analyze(ctx, bundle, opts)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
|
|
// Verify multi-agent coordination
|
|
assert.Len(t, result.Summary.AgentsUsed, 3)
|
|
assert.Contains(t, result.Summary.AgentsUsed, "local")
|
|
assert.Contains(t, result.Summary.AgentsUsed, "hosted")
|
|
assert.Contains(t, result.Summary.AgentsUsed, "ollama")
|
|
|
|
// Verify results from all agents
|
|
assert.Len(t, result.Results, 3)
|
|
|
|
agentResults := make(map[string]*AnalyzerResult)
|
|
for _, r := range result.Results {
|
|
agentResults[r.AgentName] = r
|
|
}
|
|
|
|
assert.Contains(t, agentResults, "local")
|
|
assert.Contains(t, agentResults, "hosted")
|
|
assert.Contains(t, agentResults, "ollama")
|
|
|
|
// Verify summary counts
|
|
assert.Equal(t, 1, result.Summary.PassCount)
|
|
assert.Equal(t, 1, result.Summary.WarnCount)
|
|
assert.Equal(t, 1, result.Summary.FailCount)
|
|
|
|
// Verify remediation from LLM agent
|
|
assert.NotEmpty(t, result.Remediation)
|
|
assert.Equal(t, "ai-suggested", result.Remediation[0].Category)
|
|
}
|
|
|
|
// TestPhase2_AgentFallback tests fallback mechanisms
|
|
func TestPhase2_AgentFallback(t *testing.T) {
|
|
ctx := context.Background()
|
|
engine := NewAnalysisEngine()
|
|
|
|
// Register agents with different availability
|
|
availableAgent := &integrationTestAgent{
|
|
name: "available",
|
|
available: true,
|
|
results: []*AnalyzerResult{
|
|
{IsPass: true, Title: "Available Agent Result", AgentName: "available"},
|
|
},
|
|
}
|
|
|
|
unavailableAgent := &integrationTestAgent{
|
|
name: "unavailable",
|
|
available: false,
|
|
}
|
|
|
|
require.NoError(t, engine.RegisterAgent("available", availableAgent))
|
|
require.NoError(t, engine.RegisterAgent("unavailable", unavailableAgent))
|
|
|
|
bundle := &SupportBundle{
|
|
Files: map[string][]byte{"test.json": []byte(`{}`)},
|
|
Metadata: &SupportBundleMetadata{CreatedAt: time.Now()},
|
|
}
|
|
|
|
// Test with mixed availability
|
|
opts := AnalysisOptions{
|
|
Agents: []string{"available", "unavailable"},
|
|
}
|
|
|
|
result, err := engine.Analyze(ctx, bundle, opts)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
|
|
// Should only use available agent
|
|
assert.Len(t, result.Summary.AgentsUsed, 1)
|
|
assert.Contains(t, result.Summary.AgentsUsed, "available")
|
|
assert.Len(t, result.Results, 1)
|
|
assert.Equal(t, "available", result.Results[0].AgentName)
|
|
}
|
|
|
|
// TestPhase2_AgentHealthCheck tests health checking for all agent types
|
|
func TestPhase2_AgentHealthCheck(t *testing.T) {
|
|
ctx := context.Background()
|
|
engine := NewAnalysisEngine()
|
|
|
|
// Register agents with different health states
|
|
healthyAgent := &integrationTestAgent{
|
|
name: "healthy",
|
|
available: true,
|
|
healthy: true,
|
|
}
|
|
|
|
unhealthyAgent := &integrationTestAgent{
|
|
name: "unhealthy",
|
|
available: true,
|
|
healthy: false,
|
|
error: "simulated agent error",
|
|
}
|
|
|
|
require.NoError(t, engine.RegisterAgent("healthy", healthyAgent))
|
|
require.NoError(t, engine.RegisterAgent("unhealthy", unhealthyAgent))
|
|
|
|
health, err := engine.HealthCheck(ctx)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, health)
|
|
|
|
// Should be degraded due to unhealthy agent
|
|
assert.Equal(t, "degraded", health.Status)
|
|
assert.Len(t, health.Agents, 2)
|
|
|
|
// Find and verify agent health states
|
|
healthMap := make(map[string]AgentHealth)
|
|
for _, agentHealth := range health.Agents {
|
|
healthMap[agentHealth.Name] = agentHealth
|
|
}
|
|
|
|
assert.Equal(t, "healthy", healthMap["healthy"].Status)
|
|
assert.True(t, healthMap["healthy"].Available)
|
|
|
|
assert.Equal(t, "unhealthy", healthMap["unhealthy"].Status)
|
|
assert.Equal(t, "simulated agent error", healthMap["unhealthy"].Error)
|
|
assert.True(t, healthMap["unhealthy"].Available)
|
|
}
|
|
|
|
// integrationTestAgent is used for testing (avoiding import cycles)
|
|
type integrationTestAgent struct {
|
|
name string
|
|
available bool
|
|
healthy bool
|
|
error string
|
|
results []*AnalyzerResult
|
|
}
|
|
|
|
func (a *integrationTestAgent) Name() string {
|
|
return a.name
|
|
}
|
|
|
|
func (a *integrationTestAgent) IsAvailable() bool {
|
|
return a.available
|
|
}
|
|
|
|
func (a *integrationTestAgent) Capabilities() []string {
|
|
return []string{"test-capability"}
|
|
}
|
|
|
|
func (a *integrationTestAgent) HealthCheck(ctx context.Context) error {
|
|
if !a.healthy {
|
|
if a.error != "" {
|
|
return errors.New(a.error)
|
|
}
|
|
return errors.New("agent unhealthy")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *integrationTestAgent) Analyze(ctx context.Context, data []byte, analyzers []AnalyzerSpec) (*AgentResult, error) {
|
|
if !a.available {
|
|
return nil, errors.New("agent not available")
|
|
}
|
|
|
|
return &AgentResult{
|
|
Results: a.results,
|
|
Metadata: AgentResultMetadata{
|
|
Duration: time.Millisecond * 50,
|
|
AnalyzerCount: len(analyzers),
|
|
Version: "1.0.0",
|
|
},
|
|
}, nil
|
|
}
|