diagnose

package
v0.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2026 License: AGPL-3.0 Imports: 25 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ModeAlert   = "alert"
	ModeInspect = "inspect"
)

Variables

This section is empty.

Functions

func CLIList

func CLIList(stateDir string, limit int) error

CLIList prints a summary table of recent diagnosis records from stateDir.

func CLIShow

func CLIShow(stateDir string, id string) error

CLIShow prints the full details of a specific diagnosis record.

func CleanupRecords

func CleanupRecords()

CleanupRecords removes diagnosis records exceeding the retention period or the maximum count, whichever triggers first.

func FormatReportDescription

func FormatReportDescription(record *DiagnoseRecord, report string, language string) string

FormatReportDescription builds a concise diagnosis report suitable for notification description fields (max 2048 bytes). It prioritizes:

  1. Header (plugin, target, time, status)
  2. AI summary/report body
  3. A footer with record ID and CLI hint

If the report exceeds 2048 bytes, the AI body is truncated.

func FormatToolArgsDisplay

func FormatToolArgsDisplay(name, rawArgs string) string

FormatToolArgsDisplay produces a human-friendly summary of tool arguments for terminal display. Returns "" when there is nothing meaningful to show.

func Init

func Init(registry *ToolRegistry)

Init initializes the global diagnose engine and aggregator. Called once at startup from the agent package.

func ParseArgs

func ParseArgs(raw string) map[string]string

ParseArgs parses the AI's function call arguments JSON into a flat string map. Returns a non-nil empty map for empty input so callers can safely read keys.

func ParseToolArgs

func ParseToolArgs(raw string) map[string]string

ParseToolArgs parses the nested tool_args JSON string (from call_tool). Returns nil for empty input; callers should check for nil.

func RunSelfTest

func RunSelfTest(registry *ToolRegistry, filter string, verbose bool) error

RunSelfTest executes all local tools in the registry and reports results. Returns a non-nil error if any tool FAILs, so the caller can decide the exit code.

func SeverityRank

func SeverityRank(status string) int

SeverityRank returns a numeric rank for severity comparison. Higher rank = more severe.

func Shutdown

func Shutdown()

Shutdown gracefully stops the diagnose subsystem.

func TruncateForRecord

func TruncateForRecord(s string) string

TruncateForRecord truncates tool output for storage in DiagnoseRecord. Allows a larger budget than TruncateOutput since records are for audit.

func TruncateOutput

func TruncateOutput(s string) string

TruncateOutput ensures a tool's output doesn't exceed the maximum size for sending to the AI. Uses UTF-8-safe truncation.

func TruncateUTF8

func TruncateUTF8(s string, maxBytes int) string

TruncateUTF8 truncates s to at most maxBytes bytes without breaking multi-byte UTF-8 characters. Exported for reuse across packages.

Types

type AIRecord

type AIRecord struct {
	Model        string `json:"model"`
	TotalRounds  int    `json:"total_rounds"`
	InputTokens  int    `json:"input_tokens"`
	OutputTokens int    `json:"output_tokens"`
}

AIRecord stores AI model usage info for this diagnosis.

type AccessorFactory

type AccessorFactory func(ctx context.Context, instanceRef any) (any, error)

AccessorFactory creates a shared Accessor for a remote plugin. The engine calls this once per DiagnoseSession.

type AlertRecord

type AlertRecord struct {
	Plugin string          `json:"plugin"`
	Target string          `json:"target"`
	Checks []CheckSnapshot `json:"checks"`
}

AlertRecord stores the alert context that triggered the diagnosis.

type ChatStream added in v0.1.2

type ChatStream struct {
	// contains filtered or unexported fields
}

ChatStream manages a multi-turn chat conversation with history. Unlike DiagnoseEngine which processes single diagnosis requests, ChatStream maintains conversation state across multiple user messages.

func NewChatStream added in v0.1.2

func NewChatStream(cfg ChatStreamConfig) *ChatStream

NewChatStream creates a chat stream with the given configuration.

func (*ChatStream) HandleMessage added in v0.1.2

func (s *ChatStream) HandleMessage(ctx context.Context, input string) (reply string, usage aiclient.Usage, err error)

HandleMessage processes one user message through the conversation. On error, the user message is rolled back from history.

func (*ChatStream) Messages added in v0.1.2

func (s *ChatStream) Messages() []aiclient.Message

Messages returns the current message history. Useful for inspection or persistence.

func (*ChatStream) Reset added in v0.1.2

func (s *ChatStream) Reset()

Reset clears the conversation history except for the system prompt.

type ChatStreamConfig added in v0.1.2

type ChatStreamConfig struct {
	FC                 *aiclient.FailoverClient
	Registry           *ToolRegistry
	ToolTimeout        time.Duration
	SystemPrompt       string
	AllowShell         bool
	ShellExecutor      ShellExecutor
	ProgressCallback   ProgressCallback
	ContextWindowLimit int
	GatewayMetadata    aiclient.GatewayMetadata
}

ChatStreamConfig configures a new ChatStream.

type CheckSnapshot

type CheckSnapshot struct {
	Check         string `json:"check"`
	Status        string `json:"status"`
	CurrentValue  string `json:"current_value"`
	ThresholdDesc string `json:"threshold_desc,omitempty"`
	Description   string `json:"description"`
}

CheckSnapshot captures the current state of one alerting check at the moment the diagnosis is triggered. Produced by Gather(), consumed by the DiagnoseEngine.

func ExtractCheckSnapshot

func ExtractCheckSnapshot(event *types.Event) CheckSnapshot

ExtractCheckSnapshot builds a CheckSnapshot from an event's labels.

type DiagnoseAggregator

type DiagnoseAggregator struct {
	// contains filtered or unexported fields
}

DiagnoseAggregator collects alerts for the same target within a short time window, then submits one aggregated DiagnoseRequest to the engine.

func GlobalAggregator

func GlobalAggregator() *DiagnoseAggregator

GlobalAggregator returns the singleton aggregator, or nil if not initialized.

func NewDiagnoseAggregator

func NewDiagnoseAggregator(engine *DiagnoseEngine, window time.Duration) *DiagnoseAggregator

NewDiagnoseAggregator creates an aggregator with the given window duration.

func (*DiagnoseAggregator) Shutdown

func (a *DiagnoseAggregator) Shutdown()

Shutdown cancels all pending aggregation timers.

func (*DiagnoseAggregator) Submit

func (a *DiagnoseAggregator) Submit(event *types.Event, snapshot CheckSnapshot, pluginName string, instanceRef any, diagnoseConfig config.DiagnoseConfig)

Submit is called from the alerting engine when an alert event is produced. It aggregates events for the same (plugin, target) within the time window.

type DiagnoseEngine

type DiagnoseEngine struct {
	// contains filtered or unexported fields
}

DiagnoseEngine is the central coordinator for AI-powered diagnosis.

func GlobalEngine

func GlobalEngine() *DiagnoseEngine

GlobalEngine returns the singleton engine, or nil if not initialized.

func NewDiagnoseEngine

func NewDiagnoseEngine(registry *ToolRegistry, cfg config.AIConfig) *DiagnoseEngine

NewDiagnoseEngine creates a new engine from global config.

func (*DiagnoseEngine) Registry

func (e *DiagnoseEngine) Registry() *ToolRegistry

Registry returns the engine's tool registry.

func (*DiagnoseEngine) ReleaseSem

func (e *DiagnoseEngine) ReleaseSem()

ReleaseSem releases one concurrency slot previously acquired via TrySem.

func (*DiagnoseEngine) RunDiagnose

func (e *DiagnoseEngine) RunDiagnose(req *DiagnoseRequest) *DiagnoseRecord

RunDiagnose is the goroutine entry point. It includes panic recovery, session lifecycle, cooldown update, and state persistence. Returns the DiagnoseRecord so callers (e.g. inspect CLI) can inspect results.

func (*DiagnoseEngine) RunDiagnoseStreaming

func (e *DiagnoseEngine) RunDiagnoseStreaming(ctx context.Context, req *DiagnoseRequest, cb StreamCallback) (report string, err error)

RunDiagnoseStreaming runs a diagnosis with streaming output via cb. Unlike RunDiagnose, it uses the caller-provided ctx (can be cancelled externally), does not manage sem/cooldown/state, and does not save records to disk. Intended for remote sessions where the server manages lifecycle.

func (*DiagnoseEngine) Shutdown

func (e *DiagnoseEngine) Shutdown()

Shutdown cancels all in-flight diagnoses for graceful termination.

func (*DiagnoseEngine) State

func (e *DiagnoseEngine) State() *DiagnoseState

State returns the engine's state for external inspection.

func (*DiagnoseEngine) Submit

func (e *DiagnoseEngine) Submit(req *DiagnoseRequest)

Submit attempts to schedule a diagnosis. It respects cooldown, daily token limits, and concurrency bounds. Returns immediately; actual diagnosis runs in a goroutine.

func (*DiagnoseEngine) TrySem

func (e *DiagnoseEngine) TrySem() bool

TrySem attempts to acquire a concurrency slot (shared with local diagnoses). Returns true if acquired, false if all slots are occupied.

type DiagnoseRecord

type DiagnoseRecord struct {
	ID         string        `json:"id"`
	Mode       string        `json:"mode"`   // "alert" or "inspect"
	Status     string        `json:"status"` // success, failed, cancelled, timeout
	Error      string        `json:"error,omitempty"`
	CreatedAt  time.Time     `json:"created_at"`
	DurationMs int64         `json:"duration_ms"`
	Alert      AlertRecord   `json:"alert"`
	AI         AIRecord      `json:"ai"`
	Rounds     []RoundRecord `json:"rounds"`
	Report     string        `json:"report,omitempty"`
}

DiagnoseRecord stores the full trace of a single diagnosis run, written as a JSON file under state.d/diagnoses/.

func NewDiagnoseRecord

func NewDiagnoseRecord(req *DiagnoseRequest) *DiagnoseRecord

NewDiagnoseRecord creates a DiagnoseRecord from a DiagnoseRequest, pre-populating the alert context and timestamp.

func (*DiagnoseRecord) FilePath

func (r *DiagnoseRecord) FilePath() string

FilePath returns the absolute path where this record is (or will be) stored.

func (*DiagnoseRecord) Save

func (r *DiagnoseRecord) Save() error

Save writes the DiagnoseRecord atomically (temp file + rename) to the diagnoses directory.

type DiagnoseRequest

type DiagnoseRequest struct {
	Mode         string // "alert" (default) or "inspect"
	Events       []*types.Event
	Plugin       string
	Target       string
	RuntimeOS    string
	Checks       []CheckSnapshot
	InstanceRef  any
	Timeout      time.Duration
	Cooldown     time.Duration
	Descriptions string           // remote diagnose: textual alert descriptions for AI context
	OnProgress   ProgressCallback // optional; nil means no progress output
}

DiagnoseRequest is produced by the DiagnoseAggregator after collecting alerts for the same target within the aggregation window. For inspect mode, Events and Checks are nil.

type DiagnoseSession

type DiagnoseSession struct {
	Accessor  any // shared remote Accessor, created by the plugin's factory
	Record    *DiagnoseRecord
	StartTime time.Time
	// contains filtered or unexported fields
}

DiagnoseSession manages the lifecycle of a single diagnosis run. All remote tool calls within the same diagnosis share one Accessor (TCP connection).

func (*DiagnoseSession) Close

func (s *DiagnoseSession) Close()

Close releases the shared Accessor if it implements io.Closer.

type DiagnoseState

type DiagnoseState struct {
	Date         string           `json:"date"`
	InputTokens  int              `json:"input_tokens"`
	OutputTokens int              `json:"output_tokens"`
	Cooldowns    map[string]int64 `json:"cooldowns"` // "plugin::target" → unix timestamp
	// contains filtered or unexported fields
}

DiagnoseState tracks daily token usage and per-target cooldowns. Persisted to state.d/diagnose_state.json for restart resilience.

func NewDiagnoseState

func NewDiagnoseState() *DiagnoseState

NewDiagnoseState creates a fresh state for today.

func (*DiagnoseState) AddTokens

func (s *DiagnoseState) AddTokens(input, output int)

func (*DiagnoseState) FormatUsage

func (s *DiagnoseState) FormatUsage() string

FormatUsage returns a human-readable summary of daily token usage.

func (*DiagnoseState) IsCooldownActive

func (s *DiagnoseState) IsCooldownActive(plugin, target string) bool

func (*DiagnoseState) IsDailyLimitReached

func (s *DiagnoseState) IsDailyLimitReached(limit int) bool

IsDailyLimitReached checks if the daily token budget has been exhausted.

func (*DiagnoseState) Load

func (s *DiagnoseState) Load()

Load reads state from state.d/diagnose_state.json. Missing file is not an error.

func (*DiagnoseState) Save

func (s *DiagnoseState) Save()

Save atomically writes state to disk.

func (*DiagnoseState) TotalTokens

func (s *DiagnoseState) TotalTokens() int

func (*DiagnoseState) UpdateCooldown

func (s *DiagnoseState) UpdateCooldown(plugin, target string, duration time.Duration)

type DiagnoseTool

type DiagnoseTool struct {
	Name        string      `json:"name"`
	Description string      `json:"description"`
	Parameters  []ToolParam `json:"parameters,omitempty"`
	Scope       ToolScope   `json:"-"`
	SupportedOS []string    `json:"-"`

	Execute       func(ctx context.Context, args map[string]string) (string, error)                           `json:"-"`
	RemoteExecute func(ctx context.Context, session *DiagnoseSession, args map[string]string) (string, error) `json:"-"`
}

DiagnoseTool defines a diagnostic tool that the AI can invoke.

func (DiagnoseTool) SupportsOS

func (t DiagnoseTool) SupportsOS(goos string) bool

SupportsOS reports whether the tool should be exposed on the given OS. Empty SupportedOS means all operating systems are allowed.

type ProgressCallback

type ProgressCallback func(event ProgressEvent)

ProgressCallback receives progress events during a diagnosis run. A nil callback is safe; the engine simply skips the call.

type ProgressEvent

type ProgressEvent struct {
	Type       ProgressEventType
	Round      int
	ToolName   string
	ToolArgs   string
	Reasoning  string        // non-empty on AIDone when the model emits reasoning
	Duration   time.Duration // set on AIDone / ToolDone
	ResultLen  int           // set on ToolDone
	IsError    bool          // set on ToolDone
	ToolOutput string        // set on ToolDone; the tool result content
}

ProgressEvent carries details about one progress milestone in a diagnosis run.

type ProgressEventType

type ProgressEventType int

ProgressEventType identifies the kind of progress event fired by the engine.

const (
	ProgressAIStart   ProgressEventType = iota // AI call starting (spinner opportunity)
	ProgressAIDone                             // AI call completed
	ProgressToolStart                          // Tool invocation starting
	ProgressToolDone                           // Tool invocation completed
)

type RoundRecord

type RoundRecord struct {
	Round       int              `json:"round"`
	ToolCalls   []ToolCallRecord `json:"tool_calls,omitempty"`
	AIReasoning string           `json:"ai_reasoning,omitempty"`
}

RoundRecord stores one round of AI interaction.

type SelfTestResult

type SelfTestResult struct {
	Category string
	Tool     string
	Status   string // PASS, FAIL, SKIP, WARN
	Duration time.Duration
	OutSize  int
	Message  string
	Args     map[string]string
}

SelfTestResult holds the outcome of testing one tool.

type ShellExecutor added in v0.1.2

type ShellExecutor interface {
	// ExecuteShell executes a shell command with approval flow.
	// Returns (output, approved, error).
	// approved=false indicates user rejected execution.
	ExecuteShell(ctx context.Context, command string, timeout time.Duration) (string, bool, error)
}

ShellExecutor is an interface for executing shell commands during chat. Implementations must handle user approval and command editing.

type StreamCallback

type StreamCallback func(delta, stage string, done bool, metadata map[string]any)

StreamCallback receives streaming output during a remote diagnosis run. Called with incremental deltas; done=true on the final call. A nil callback is safe; the engine simply skips the call.

type ToolCallRecord

type ToolCallRecord struct {
	Name       string            `json:"name"`
	Args       map[string]string `json:"args,omitempty"`
	Result     string            `json:"result"`
	DurationMs int64             `json:"duration_ms"`
}

ToolCallRecord stores one tool invocation within a round.

type ToolCategory

type ToolCategory struct {
	Name        string         // "redis", "disk", "cpu"
	Plugin      string         // source plugin name
	Description string         // one-line description for AI
	Scope       ToolScope      // local or remote
	Tools       []DiagnoseTool // tools in this category
}

ToolCategory groups related diagnostic tools under a plugin.

type ToolParam

type ToolParam struct {
	Name        string `json:"name"`
	Type        string `json:"type"` // "string", "int"
	Description string `json:"description"`
	Required    bool   `json:"required"`
}

ToolParam describes a single parameter accepted by a DiagnoseTool.

type ToolRegistry

type ToolRegistry struct {
	// contains filtered or unexported fields
}

ToolRegistry manages all diagnostic tools registered by plugins. Thread-safe for concurrent reads; writes happen only at startup.

func NewToolRegistry

func NewToolRegistry() *ToolRegistry

NewToolRegistry creates an empty registry.

func (*ToolRegistry) ByPlugin

func (r *ToolRegistry) ByPlugin(plugin string) []DiagnoseTool

ByPlugin returns all tools registered under the given plugin/category name.

func (*ToolRegistry) ByPluginForOS

func (r *ToolRegistry) ByPluginForOS(plugin, goos string) []DiagnoseTool

ByPluginForOS returns tools under the given plugin/category that are supported on the specified operating system.

func (*ToolRegistry) Categories

func (r *ToolRegistry) Categories() []string

Categories returns a sorted snapshot of all category names.

func (*ToolRegistry) CategoriesWithTools

func (r *ToolRegistry) CategoriesWithTools() []ToolCategory

CategoriesWithTools returns all categories with their tools, sorted by category name.

func (*ToolRegistry) CreateAccessor

func (r *ToolRegistry) CreateAccessor(ctx context.Context, plugin string, instanceRef any) (any, error)

CreateAccessor calls the registered factory for the given plugin.

func (*ToolRegistry) Get

func (r *ToolRegistry) Get(name string) (*DiagnoseTool, bool)

Get returns a tool by name.

func (*ToolRegistry) HasAccessorFactory

func (r *ToolRegistry) HasAccessorFactory(plugin string) bool

HasAccessorFactory reports whether a plugin has a registered accessor factory.

func (*ToolRegistry) ListAllTools

func (r *ToolRegistry) ListAllTools() string

ListAllTools returns a compact catalog of every registered tool, grouped by category and sorted alphabetically. Designed for injection into AI prompts so the model can call tools directly without a discovery round-trip.

func (*ToolRegistry) ListCategories

func (r *ToolRegistry) ListCategories() string

ListCategories returns a formatted string of all categories for the AI, sorted alphabetically for deterministic output.

func (*ToolRegistry) ListCategoriesForOS

func (r *ToolRegistry) ListCategoriesForOS(goos string) string

ListCategoriesForOS returns a formatted string of categories that support the specified operating system.

func (*ToolRegistry) ListToolCatalogSmart

func (r *ToolRegistry) ListToolCatalogSmart() string

ListToolCatalogSmart returns a hybrid catalog for diagnose prompts:

  • Built-in tool categories: full detail (name, params, description)
  • MCP categories (prefix "mcp:"): summary only (name, tool count, description)

This keeps the prompt concise when many MCP tools are registered while still allowing zero-roundtrip access to built-in tools.

func (*ToolRegistry) ListToolCatalogSmartForOS

func (r *ToolRegistry) ListToolCatalogSmartForOS(goos string) string

ListToolCatalogSmartForOS returns the tool catalog filtered by supported OS.

func (*ToolRegistry) ListTools

func (r *ToolRegistry) ListTools(category string) string

ListTools returns a formatted string of tools in a category for the AI.

func (*ToolRegistry) ListToolsForOS

func (r *ToolRegistry) ListToolsForOS(category, goos string) string

ListToolsForOS returns tools in a category filtered by supported OS.

func (*ToolRegistry) Register

func (r *ToolRegistry) Register(category string, tool DiagnoseTool)

Register adds a tool under the given category. If the category doesn't exist, it is created with the provided scope and description. Duplicate tool names are logged and skipped (programming error, not runtime condition).

func (*ToolRegistry) RegisterAccessorFactory

func (r *ToolRegistry) RegisterAccessorFactory(plugin string, factory AccessorFactory)

RegisterAccessorFactory registers a factory that creates a shared Accessor for remote plugin tools within a DiagnoseSession.

func (*ToolRegistry) RegisterCategory

func (r *ToolRegistry) RegisterCategory(name, plugin, description string, scope ToolScope)

RegisterCategory registers or updates a category's metadata.

func (*ToolRegistry) ToolCount

func (r *ToolRegistry) ToolCount() int

ToolCount returns the total number of registered tools.

func (*ToolRegistry) ToolSupportedOn

func (r *ToolRegistry) ToolSupportedOn(name, goos string) bool

ToolSupportedOn reports whether the named tool should be available on the specified operating system.

type ToolScope

type ToolScope int

ToolScope distinguishes where a diagnostic tool executes.

const (
	ToolScopeLocal  ToolScope = iota // Executes on the catpaw host (disk, cpu, mem)
	ToolScopeRemote                  // Needs a connection to the remote target (redis, mysql)
)

func (ToolScope) String

func (s ToolScope) String() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL